Bapati
DevOpsDocker

Docker Nedir? Konteyner, Image ve Dockerfile Temelleri

27 Temmuz 2026
6 dk okuma
Docker Nedir? Konteyner, Image ve Dockerfile Temelleri

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 makineKonteyner
Kendi çekirdeğiVarYok — ana makineyi paylaşır
Tipik boyutGB'larMB'lar
Başlangıç süresiDakikalarSaniyeler
Aynı sunucuda kaç taneOnlarcaYüzlerce
İzolasyon sınırıDonanım düzeyi — keskinSü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:

KararKazanç
.csproj'ları önce kopyalamakKod değiştiğinde restore yeniden çalışmaz — build dakikalardan saniyelere iner
Çok aşamalı buildDerleme araçları nihai imaja girmez — boyut ~3 kat küçülür
USER bapatiContainer 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

HataSonucuDoğrusu
Parolayı Dockerfile'a yazmakKatmanlarda kalıcı olur, imajı alan herkes görürOrtam değişkeni ya da secret
latest etiketiBugün çalışan yapı yarın farklı sürümle gelirSürümü sabitle: postgres:16.2
root olarak çalıştırmakContainer ele geçerse yetki tamUSER ile yetkisiz kullanıcı
Container'a SSH ile bağlanmakDeğişiklik kayıt dışı kalır, yeniden üretilemezDeğişikliği Dockerfile'a yaz
Log'u dosyaya yazmakContainer silinince log da giderstdout'a yaz, Docker toplasın
.dockerignore yokBuild bağlamı yüzlerce MB olurbin, obj, node_modules hariç
Sağlık kontrolü yokÖlü container ayakta görünürHEALTHCHECK 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 imajYaklaşık boyutNot
dotnet/sdk:8.0~800 MBYalnızca derleme aşamasında
dotnet/aspnet:8.0~220 MBStandart çalışma imajı
dotnet/aspnet:8.0-alpine~110 MBDaha küçük, farklı libc
node:20~1,1 GBTam araç zinciri
node:20-alpine~140 MBYerel 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

Docker

Bir projeniz mi var?
Birlikte hayata geçirelim!

Dijital Dönüşümünüzü Başlatın

Yazılım geliştirme, sistem altyapısı, teknik danışmanlık veya eğitim! Hangi alanda ihtiyacınız varsa hemen görüşelim. İlk adımı siz atın, gerisini birlikte çözelim!