Docker nedir, konteyner ile sanal makine arasındaki fark ne? Image, Dockerfile, katman mantığı ve Compose temelleri sade anlatımla ele alınıyor.
Çözdüğü Problem: "Bende Çalışıyordu"
Bir uygulamanın geliştirici makinesinde çalışıp sunucuda çalışmaması, yazılım dünyasının en eski sürtüşmelerinden biridir. Sebep genellikle sürüm, bağımlılık ya da yapılandırma farkıdır — makinede kurulu olan ama kimsenin farkında olmadığı bir kütüphane, farklı bir çalışma zamanı sürümü, eksik bir ortam değişkeni.
Docker bu problemi kökten ele alır: uygulamayı yalnızca kodla değil, çalışması için gereken her şeyle birlikte paketler. Sonuç olarak aynı paket, geliştiricinin dizüstünde de, test sunucusunda da, bulutta da aynı şekilde davranır.
Konteyner ve Sanal Makine Aynı Şey Değil
Sanal makine, kendi işletim sistemi çekirdeğini taşır. Konteyner ise ana makinenin çekirdeğini paylaşır; yalnızca uygulama ve bağımlılıkları izole edilir.
| Sanal makine | Konteyner | |
|---|---|---|
| Kendi çekirdeği | Var | Yok — ana makineyi paylaşır |
| Tipik boyut | GB'lar | MB'lar |
| Başlangıç süresi | Dakikalar | Saniyeler |
| Aynı sunucuda kaç tane | Onlarca | Yüzlerce |
| İzolasyon sınırı | Donanım düzeyi — keskin | Süreç düzeyi — daha yumuşak |
| Farklı işletim sistemi | Çalıştırabilir | Çekirdek uyumu gerekir |
Konteynerler çekirdek düzeyinde tam izolasyon sunmaz. Güvenlik sınırı sanal makine kadar keskin değildir; güvenilmeyen kodu çalıştırıyorsanız bu ayrım mimari kararlarda belirleyici olur.
Image ve Container İlişkisi
Image, çalıştırılabilir bir şablondur: salt okunur, değişmez bir paket. Container ise o image'ın çalışan bir örneğidir. Sınıf ve nesne ilişkisine benzetmek, kavramı oturtmak için pratik bir yoldur.
# Image indir (şablon)
docker pull mcr.microsoft.com/dotnet/aspnet:8.0
# Aynı image'dan üç bağımsız container
docker run -d --name api-1 -p 5001:8080 bapati-api:1.4.0
docker run -d --name api-2 -p 5002:8080 bapati-api:1.4.0
docker run -d --name api-3 -p 5003:8080 bapati-api:1.4.0
# Ne çalışıyor, ne kadar yer kaplıyor?
docker ps
docker image ls
docker stats --no-stream
Her container kendi yazılabilir katmanına sahiptir ve birbirinden bağımsız çalışır. api-1 içinde bir dosya oluşturmanız api-2'yi etkilemez — ve api-1 silindiğinde o dosya da gider.
Dockerfile ve Katman Mantığı
Dockerfile, image'ın nasıl oluşturulacağını anlatan tarif dosyasıdır. Her satır image'a yeni bir katman ekler ve katmanlar önbelleğe alınır. Bu nedenle nadiren değişen adımları üste, sık değişen adımları alta yazmak build sürelerini belirgin biçimde kısaltır.
# --- 1. AŞAMA: derleme ---
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS derleme
WORKDIR /kaynak
# Önce SADECE proje dosyaları: bağımlılıklar nadiren değişir,
# bu katman kaynak kod değiştiğinde önbellekten gelir.
COPY *.sln .
COPY BapatiApi.API/*.csproj BapatiApi.API/
COPY BapatiApi.Application/*.csproj BapatiApi.Application/
RUN dotnet restore
# Kaynak kod en sona: her commit'te değişen tek katman bu olsun
COPY . .
RUN dotnet publish BapatiApi.API/BapatiApi.API.csproj -c Release -o /cikti
# --- 2. AŞAMA: çalışma ---
# SDK (~800 MB) nihai imaja girmez, yalnızca runtime (~220 MB) kalır
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /uygulama
COPY --from=derleme /cikti .
# root olarak çalıştırma
RUN adduser --disabled-password --gecos "" bapati
USER bapati
EXPOSE 8080
ENTRYPOINT ["dotnet", "BapatiApi.API.dll"]
Bu dosyadaki üç karar, üç ayrı sorunu çözüyor:
| Karar | Kazanç |
|---|---|
.csproj'ları önce kopyalamak | Kod değiştiğinde restore yeniden çalışmaz — build dakikalardan saniyelere iner |
| Çok aşamalı build | Derleme araçları nihai imaja girmez — boyut ~3 kat küçülür |
USER bapati | Container ele geçirilse bile root yetkisi yok |
Bir de .dockerignore dosyası şart: onsuz COPY . . satırı node_modules, bin, obj ve .git klasörlerini de build bağlamına taşır ve her build'i yavaşlatır.
# .dockerignore
**/bin
**/obj
**/node_modules
**/.git
**/.vs
**/*.user
**/appsettings.Development.json
Veri Nerede Durur? Volume Meselesi
Container silindiğinde içindeki yazılabilir katman da silinir. Veritabanı verisini container içinde tutmak bu nedenle kalıcı bir çözüm değildir.
# YANLIŞ: container silindiğinde veri de gider
docker run -d --name db postgres:16
# DOĞRU: veri adlandırılmış bir volume'da, container'dan bağımsız
docker volume create bapati-db-veri
docker run -d --name db \
-v bapati-db-veri:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD_FILE=/run/secrets/db_sifre \
postgres:16
# Container'ı sil, yeniden oluştur — veri yerinde
docker rm -f db
docker run -d --name db -v bapati-db-veri:/var/lib/postgresql/data postgres:16
Kalıcı olması gereken her şeyin volume üzerinde tutulması, en sık atlanan ama en pahalıya mal olan kurallardan biridir. Volume'un iki türü vardır: adlandırılmış volume (Docker yönetir, üretim için uygundur) ve bind mount (ana makinedeki bir klasörü bağlar, geliştirmede kod paylaşmak için pratiktir).
Docker Compose ile Çoklu Servis
Gerçek uygulamalar tek konteynerden oluşmaz: API, veritabanı, önbellek ve belki bir mesaj kuyruğu bir arada çalışır. Compose, bu servisleri tek bir yapılandırma dosyasında tanımlamanızı ve hepsini tek komutla ayağa kaldırmanızı sağlar.
services:
api:
build: .
ports:
- "5000:8080"
environment:
ConnectionStrings__Varsayilan: "Host=db;Database=bapati;Username=bapati"
depends_on:
db:
condition: service_healthy # sadece "başladı" değil, "hazır"
restart: unless-stopped
db:
image: postgres:16
volumes:
- db-veri:/var/lib/postgresql/data
environment:
POSTGRES_DB: bapati
POSTGRES_USER: bapati
POSTGRES_PASSWORD: ${DB_SIFRE} # .env dosyasından, dosyaya yazılmaz
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bapati"]
interval: 5s
retries: 5
volumes:
db-veri:
Dikkat edilecek iki nokta var. Birincisi: API, veritabanına localhost ile değil servis adıyla (Host=db) bağlanır — Compose her servis için ağ içinde bir ad çözümlemesi kurar. İkincisi: depends_on tek başına yalnızca "başlatma sırasını" belirler; veritabanının bağlantı kabul etmeye hazır olmasını beklemek için healthcheck gerekir. Bu ayrım, "API açılışta veritabanına bağlanamıyor ama yeniden başlatınca düzeliyor" sorununun tam sebebidir.
Yeni bir geliştiricinin projeye katıldığı ilk gün ortamı dakikalar içinde kurabilmesi, çoğu ekipte Compose'un en somut faydasıdır:
git clone https://github.com/bapati/api.git
cd api
cp .env.ornek .env
docker compose up -d # hepsi ayakta
Yaygın Hatalar
| Hata | Sonucu | Doğrusu |
|---|---|---|
| Parolayı Dockerfile'a yazmak | Katmanlarda kalıcı olur, imajı alan herkes görür | Ortam değişkeni ya da secret |
latest etiketi | Bugün çalışan yapı yarın farklı sürümle gelir | Sürümü sabitle: postgres:16.2 |
| root olarak çalıştırmak | Container ele geçerse yetki tam | USER ile yetkisiz kullanıcı |
| Container'a SSH ile bağlanmak | Değişiklik kayıt dışı kalır, yeniden üretilemez | Değişikliği Dockerfile'a yaz |
| Log'u dosyaya yazmak | Container silinince log da gider | stdout'a yaz, Docker toplasın |
.dockerignore yok | Build bağlamı yüzlerce MB olur | bin, obj, node_modules hariç |
| Sağlık kontrolü yok | Ölü container ayakta görünür | HEALTHCHECK tanımla |
Parola konusunda sık yapılan bir incelik hatası da şudur: bir katmanda dosya oluşturup sonraki katmanda silmek onu imajdan silmez. Katmanlar yığın gibi birikir; silinen dosya alt katmanda durmaya devam eder ve docker history ile görülebilir.
Üretime Çıkarken: Boyut, Sağlık ve Tarama
Geliştirmede çalışan bir yapılandırma üretim için yeterli değildir. Üç şey eklenir: imaj küçültme, sağlık kontrolü ve güvenlik taraması.
İmaj boyutu yalnızca disk meselesi değildir — her dağıtımda ağdan indirilir, her ölçekleme adımında yeniden çekilir. Temel imaj seçimi tek başına büyük fark yaratır:
| Temel imaj | Yaklaşık boyut | Not |
|---|---|---|
dotnet/sdk:8.0 | ~800 MB | Yalnızca derleme aşamasında |
dotnet/aspnet:8.0 | ~220 MB | Standart çalışma imajı |
dotnet/aspnet:8.0-alpine | ~110 MB | Daha küçük, farklı libc |
node:20 | ~1,1 GB | Tam araç zinciri |
node:20-alpine | ~140 MB | Yerel modüllerde derleme gerekebilir |
Sağlık kontrolü ise orkestratörün "bu container gerçekten hizmet veriyor mu" sorusunu cevaplamasını sağlar. Süreç ayakta ama uygulama kilitlenmişse, sağlık kontrolü olmadan hiçbir şey fark etmez:
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 CMD wget -qO- http://localhost:8080/saglik || exit 1
Son olarak imajı yayınlamadan önce taramak gerekir. Kendi kodunuz temiz olsa bile temel imajdaki bir kütüphane bilinen bir açık taşıyor olabilir:
# Bilinen güvenlik açıklarını tara
docker scout cves bapati-api:1.4.0
# Katman katman ne kadar yer kaplıyor, hangi adım şişiriyor?
docker history bapati-api:1.4.0 --no-trunc
Bu üç adımı dağıtım hattına (CI) koymak, sorunları üretimde değil derleme aşamasında yakalamayı sağlar.
Konteynerin Zihniyeti
Docker'ı verimli kullanmanın anahtarı bir araç değil bir yaklaşım: container'lar tek iş yapan ve gerektiğinde atılabilen birimlerdir. Bir container'ı silip yeniden oluşturmak, onu tamir etmekten daha ucuz olmalıdır.
Bu yaklaşım üç pratik kurala iner: durumu container dışında tut (volume ya da veritabanı), yapılandırmayı ortam değişkeniyle ver, ve her değişikliği imaja yaz. Bu üçü sağlandığında ölçekleme, güncelleme ve geri alma işlemlerinin hepsi tek komuta iner.
Geliştirme Ortamında Docker
Docker’ın en hızlı karşılık verdiği yer üretim değil, geliştirme ortamıdır. Bir projeye yeni katılan geliştiricinin veritabanı, önbellek ve mesaj kuyruğu kurmakla geçirdiği ilk gün, hazır bir yapılandırma dosyasıyla birkaç dakikaya iner.
Bunun ötesinde bir fayda daha var: sürüm çakışmalarının ortadan kalkması. Aynı makinede iki farklı projenin iki farklı veritabanı sürümüne ihtiyaç duyması, konteyner olmadan zahmetli bir iştir; konteynerle her proje kendi sürümünü taşır.
Geliştirme ortamında sık kullanılan bir kolaylık da kaynak kodun konteynere bağlanmasıdır. Böylece kod değişikliği için imajın yeniden oluşturulması gerekmez; dosya değişir, uygulama yeniden başlar. Bu düzen, üretimde tercih edilmez ama geliştirmede döngüyü belirgin biçimde hızlandırır.
Daha fazlası: Docker'ı uygulamalı anlattığımız bölüm 'Her Yazılımcının Bilmesi Gerekenler' serimizde; ayrıca BapatiVault serisinin final bölümünde bir .NET 8 API'sini Docker ile paketleyip dağıtıyoruz.
Videoyu sitemizde izle · YouTube'da aç · Kanala abone ol
Daha fazlası: Bir .NET uygulamasının konteynerle paketlenip dağıtıma hazırlanmasını uygulamalı gösterdiğimiz bölümle devam edebilirsiniz.
Videoyu sitemizde izle · YouTube'da aç · Bapati kanalına abone ol

