Skip to content

Bölüm 9 / 13

docker compose

Çok servisli ortam (API + PostgreSQL + Redis).

17 dakikalık okuma

Gerçek bir uygulama tek konteyner değildir: API, veritabanı, önbellek, belki bir kuyruk ve bir ters vekil. Bunları 7. dersteki gibi tek tek docker run ile ayağa kaldırmak üçüncü serviste dayanılmaz olur — komutlar birinin kafasında kalır, ağ kurmayı unutan yeni arkadaş bir gün kaybeder. Compose bu düzeni depoya girebilen tek bir dosyaya yazar: git clone + docker compose up ile projeyi çalıştırmak, 16. hattaki README'nin beş dakika kuralının mekanizmasıdır. Bu derste ayrıca 7. dersin "çalışıyor ≠ hazır" senedi ödenir: healthcheck + depends_on: service_healthy.

01Ne öğreneceksin

  • Compose'un çözdüğü sorun ve bildirimsel olmanın pratik anlamı
  • compose.yaml anatomisi: services, image ↔ build, ports, environment ↔ env_file, volumes, networks, depends_on, healthcheck, profiles
  • API + PostgreSQL + Redis tam örneği; servis adıyla DNS (7. ders) ve bağlantı dizesi
  • condition: service_healthy — "çalışıyor ≠ hazır" senedinin ödenmesi
  • Gündelik komutlar: up -d --build, logs -f, exec, ps, config, down ↔ down -v
  • Geliştirme ↔ üretim ayrımı: compose.override.yaml, bind mount ile canlı kod, profiles, watch
  • .env ve değişken ikamesi (${AD:-varsayılan}); ölçekleme ve compose'un sınırı; compose ↔ Testcontainers kararı

02Compose ne çözer

7. dersin sonundaki düzeni hatırla: bir ağ oluştur, PostgreSQL'i doğru ortam değişkenleri ve volume ile başlat, Redis'i başlat, API'yi aynı ağa bağla, portları yayımla. Dört komut, on beş bayrak ve sırayı bilen tek kişi. Compose bunların hepsini bir dosyaya alır:

Elle docker runCompose
Komutlar kafada / kişisel notlardaDosya depoda, sürüm kontrolünde, PR'da incelenir
Ağ ve volume'ü elle kurmayı hatırlaman gerekirProje için ağ ve volume kendiliğinden oluşur
Kaldırma: her konteyneri tek tek rmdocker compose down — hepsi, ağ dâhil
Bir servisin ayarını değiştirmek = komutu yeniden yazmakDosyayı düzelt, up -d — yalnız değişen servis yeniden oluşur
Yeni ekip arkadaşı için kurulum dokümanı yazılır (ve bayatlar)clone + up: doküman çalıştırılabilir

Son satır 16. hattın README beş dakika kuralı senedidir: bir projeye yeni katılan birinin beş dakikada çalışır hâle gelmesi bir niyet değil, bir mekanizma ister. Compose dosyası bayatlamayan kurulum dokümanıdır — çünkü bozulursa herkesin up komutu kırılır ve aynı gün düzeltilir.

İkinci kazanç bildirimsel olmasıdır: dosya "ne olsun"u yazar, Compose mevcut durumla karşılaştırıp farkı uygular. up -d'yi ikinci kez çalıştırdığında değişmeyen servislere dokunmaz. Bu, 12. derste göreceğin Kubernetes'in kubectl apply modelinin küçük hâlidir.

Adlandırma notu: eski docker-compose (Python, ayrı ikili) yerini docker compose (Go, Docker CLI eklentisi — "Compose V2") aldı. Dosya adı olarak compose.yaml günceldir; docker-compose.yml hâlâ okunur. Dosyanın başındaki version: "3.8" satırı artık gereksizdir ve yeni sürümlerde uyarı üretir — eski örneklerde göreceksin, yenisine yazma.

03compose.yaml anatomisi

Dosya YAML'dir: girinti boşlukla yapılır (sekme hata verir), - ile liste yazılır. En üstte üç anahtar bulunur — services (zorunlu), volumes ve networks (adlandırılmış kaynakların bildirimi).

Anahtardocker run karşılığıDikkat
image: postgres:16imaj adıHazır imaj çekilir; etiket yazılır
build: . / build: {context, dockerfile, target, args}docker buildKendi imajını derler; image: ile birlikte yazılırsa üretilen imaja o ad verilir
ports: ["127.0.0.1:8080:8080"]-pYalnız hosttan erişim için; servisler arası trafikte gerekmez
expose: ["8080"]EXPOSEYalnız beyan; port yayımlamaz
environment:-eListe ya da eşleme yazımı; sır yazılmaz
env_file: [.env.local]--env-fileSırlar buraya; dosya .gitignore'da
volumes:-vad:/yol volume, ./yol:/yol bind mount
networks:--networkYazmazsan proje için varsayılan ağ kurulur (DNS çalışır)
depends_on:—Başlatma sırası; tek başına "hazır" demek değildir (05. bölüm)
healthcheck:HEALTHCHECKServisin hazır olduğunu Compose'a bildirir
restart: unless-stopped--restart7. dersin politika tablosu aynen geçerli
command: / entrypoint:imajdan sonraki komutİmajın CMD/ENTRYPOINT'ini ezer
user: "10001:10001"--userKöksüz çalıştırma (3. ders senedi)
deploy.resources.limits--memory, --cpusCompose V2 tek makinede de uygular
profiles: [araclar]—Varsayılan up'ta başlamayan isteğe bağlı servisler

Ağ konusunda en çok işe yarayan iki cümle: Compose proje için kullanıcı tanımlı bir bridge ağı kurar, yani 7. dersteki DNS kuralı burada bedavaya gelir — servis adı = host adı. API veritabanına Host=db ile bağlanır; host makinedeki localhost:5432 değil. Ve konteynerler arası trafikte ports: gerekmez; ports: yalnızca senin tarayıcın veya psql'in erişebilmesi içindir.

04Tam örnek: API + PostgreSQL + Redis

name: basvuru

services:
  api:
    build:
      context: .
      dockerfile: src/Basvuru.Api/Dockerfile
    image: basvuru-api:dev
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      ASPNETCORE_ENVIRONMENT: Development
      ASPNETCORE_URLS: http://0.0.0.0:8080
      ConnectionStrings__Basvuru: "Host=db;Port=5432;Database=basvuru;Username=basvuru;Password=${DB_PAROLA:?DB_PAROLA gerekli}"
      Redis__BaglantI: "cache:6379"
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "dotnet", "Basvuru.Api.dll", "--healthcheck"]
      interval: 30s
      timeout: 3s
      start_period: 20s
      retries: 3

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: basvuru
      POSTGRES_USER: basvuru
      POSTGRES_PASSWORD: ${DB_PAROLA:?DB_PAROLA gerekli}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init:/docker-entrypoint-initdb.d:ro
    ports:
      - "127.0.0.1:5432:5432"      # yalnız geliştirmede: psql/DBeaver bağlanabilsin
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U basvuru -d basvuru"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s
    restart: unless-stopped

  cache:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      retries: 10
    restart: unless-stopped

volumes:
  pgdata:
  redisdata:

Okurken durulacak yerler:

  • name: basvuru — proje adı. Yazmazsan dizin adı kullanılır; oluşan her şeyin öneki budur (basvuru-db-1, basvuru_pgdata, basvuru_default ağı). Depoyu iki farklı klasöre klonlarsan iki ayrı proje olur — volume'lerin neden "kaybolduğunun" en sık nedeni budur.
  • Bağlantı dizesinde Host=db — servis adı. 7. dersin ağ dersinin doğrudan sonucu. Port da 5432'dir (konteynerin kendi portu), yayımlanan port değil.
  • ConnectionStrings__Basvuru — 5. hattaki yapılandırma dersinin senedi: .NET'te çift alt çizgi, yapılandırma hiyerarşisindeki : ayıracının ortam değişkeni karşılığıdır. Aynı değeri appsettings.json'daki ConnectionStrings:Basvuru alanı olarak ezer.
  • ${DB_PAROLA:?…} — değişken yoksa Compose hata verip durur (07. bölüm). Parolanın dosyada düz yazılmamasının en ucuz yolu budur.
  • /docker-entrypoint-initdb.d — resmî PostgreSQL imajının sözleşmesi: veri dizini boşken buradaki .sql/.sh dosyalarını bir kez çalıştırır. Şema zaten varsa çalışmaz; "init betiğim koşmuyor" şikâyetinin cevabı hemen her zaman "volume dolu" olur.
  • redis --appendonly yes — Redis varsayılanda belleğe yazar; kalıcılık isteniyorsa açıkça istenir. Önbellek için gerekmeyebilir (19. hat).

05"Çalışıyor ≠ hazır": healthcheck ve depends_on

7. dersin bıraktığı senet burada ödenir. depends_on tek başına yalnız başlatma sırasını belirler: Compose önce db konteynerini başlatır, sonra api'yi. Ama bir konteynerin başlaması, içindeki PostgreSQL'in bağlantı kabul etmeye hazır olması demek değildir; ilk açılışta veri dizini hazırlanırken saniyeler geçer. Sonuç: API açılışta bağlanamayıp çöker, siz de up'ı iki kez çalıştırmayı alışkanlık hâline getirirsiniz.

    depends_on:
      db:
        condition: service_healthy     # sağlık kontrolü YEŞİL olana kadar bekle
      cache:
        condition: service_started     # yalnız başlamış olması yeter (varsayılan)
      gocmen:
        condition: service_completed_successfully   # tek seferlik iş bitsin

Bunun çalışması için bağımlı servisin bir healthcheck'i olmalıdır. Sağlık kontrolü yazarken üç kural:

  1. Doğru aracı kullan. PostgreSQL için pg_isready, Redis için redis-cli ping, SQL Server için sqlcmd -Q "SELECT 1", kendi API'n için hazır olma ucu. curl'ün imajda bulunduğunu varsayma — postgres imajında curl yoktur.
  2. CMD ↔ CMD-SHELL. CMD listeyi doğrudan çalıştırır; boru, ||, değişken genişletmesi gerekiyorsa CMD-SHELL ile tek dize yazılır.
  3. start_period ver. Bu süre içindeki başarısız denemeler retries sayacını harcamaz. Yavaş açılan uygulamalar (ilk EF Core sorgusu, JIT ısınması) bu satır olmadan gereksiz yere "unhealthy" olur.

Durumu görmek için docker compose ps yeterlidir; sağlıklı olmayan servisin son sağlık denemesi docker inspect --format '{{json .State.Health}}' basvuru-db-1 ile okunur.

Kıdemli notu: service_healthy geliştirme deneyimini düzeltir ama uygulamanın kendi yeniden deneme mantığının yerini tutmaz. Üretimde veritabanı bir anlığına düşüp kalkabilir; 4. dersteki Requires ↔ Wants tartışmasının sonucu burada da geçerlidir: başlangıç sırası bir kolaylıktır, dayanıklılık uygulamada yazılır (yeniden deneme + üstel bekleme, 19. hat). Kubernetes'te depends_on diye bir şey zaten yoktur (12. ders) — pod'lar birbirini beklemez.

06Gündelik komutlar

$ docker compose up -d                 # arka planda kaldır (dosya değiştiyse gerekeni yeniden oluşturur)
$ docker compose up -d --build         # imajı da yeniden derle
$ docker compose up --build --wait     # sağlık kontrolleri yeşillenene kadar bekle (CI için)
$ docker compose ps                    # servisler, durum, sağlık, yayımlanan portlar
$ docker compose logs -f api           # tek servisin log'u;  -f  takip,  --tail 100
$ docker compose logs -f --since 10m   # hepsi, servis adıyla renkli
$ docker compose exec api sh           # çalışan konteynere gir (7. ders: exec, attach değil)
$ docker compose exec db psql -U basvuru -d basvuru
$ docker compose run --rm api dotnet ef database update   # tek seferlik iş, yeni konteynerde
$ docker compose restart api
$ docker compose stop                  # durdur ama silme
$ docker compose down                  # konteynerleri ve ağı sil (VOLUME KALIR)
$ docker compose down -v               # volume'leri de sil  →  VERİ KAYBI
$ docker compose config                # birleşmiş, değişkenleri çözülmüş son hâli yaz
$ docker compose pull                  # imajların yeni sürümünü çek
$ docker compose top / stats

Dört ayrıntı işi kolaylaştırır:

  • down ↔ down -v: ilki günlük temizliktir, ikincisi veritabanını siler. -v'yi refleks hâline getirme — 7. dersteki prune --volumes uyarısının compose hâli. Veriyi bilerek sıfırlamak istediğinde ise doğru komut budur ("şema bozuldu, temiz başla").
  • config teşhisin ilk adımıdır: override dosyaları, .env ikamesi ve profillerden sonra Compose'un gerçekten gördüğü dosyayı basar. "Değişkenim neden boş" sorusunun cevabı burada görünür.
  • run ↔ exec: exec zaten çalışan konteynere girer; run yeni bir konteyner başlatır (migration, tek seferlik betik). --rm ile çöp bırakmaz, --no-deps ile bağımlılıkları başlatmaz.
  • Birden çok dosya: docker compose -f compose.yaml -f compose.prod.yaml up -d ile dosyalar birleştirilir (sonraki öncekini ezer); -p ile proje adı değiştirilerek aynı depodan iki bağımsız ortam (ör. test ve demo) yan yana koşturulabilir.

07Değişken ikamesi ve .env

Compose dosyasındaki ${…} ifadeleri, dosya ayrıştırılırken çözülür. Kaynak sırası: kabuk ortamı, --env-file ile verilen dosya, proje kökündeki .env.

YazımAnlamı
${DB_PAROLA}Yoksa boş dize — sessiz arıza kaynağı
${PORT:-8080}Yoksa veya boşsa 8080
${PORT-8080}Yalnız tanımsızsa 8080 (boş dize boş kalır)
${DB_PAROLA:?mesaj}Yoksa hata verip durur — sır için doğru olan

İki .env karıştırılmamalıdır ve bu, compose'un en sık yanlış anlaşılan konusudur:

  • Proje kökündeki .env dosyası compose dosyasının kendisindeki ${…} ifadelerini besler. Konteynerin içine kendiliğinden girmez.
  • env_file: ile verilen dosya ise doğrudan konteynerin ortam değişkenlerine yazılır.

13. hattaki sır kuralı burada da geçerlidir: .env depoya girmez. .gitignore'a eklenir, yanına örnek olarak değerleri boşaltılmış bir .env.example konur ve README onu kopyalatır. Sunucuda dosyanın izni 640 yapılır (3. ders). Üretimde daha iyisi, ortam değişkenini sır yöneticisinden almaktır (13. hat).

# .env.example  (depoya girer)
DB_PAROLA=
API_PORT=8080

# .gitignore
.env
.env.local

08Geliştirme ↔ üretim ayrımı

Aynı dosyanın iki ortamı da idare etmesi beklenmez. Compose'un yolu katmanlamadır: compose.yaml ortak tanımı, compose.override.yaml ise geliştirmeye özgü eklemeleri tutar ve hiçbir bayrak yazmasan da kendiliğinden okunur. Üretim için ayrı bir dosya açıkça verilir.

# compose.override.yaml  — yalnız geliştirmede
services:
  api:
    build:
      target: debug              # Dockerfile'daki SDK aşamasında dur
    volumes:
      - ./src:/src:cached        # canlı kod (bind mount)
    command: ["dotnet", "watch", "--project", "/src/Basvuru.Api", "run"]
    environment:
      ASPNETCORE_ENVIRONMENT: Development
      DOTNET_USE_POLLING_FILE_WATCHER: "1"    # bind mount'ta dosya olayları gelmeyebilir
  db:
    ports: ["127.0.0.1:5432:5432"]

  adminer:                        # isteğe bağlı araç
    image: adminer
    profiles: ["araclar"]
    ports: ["127.0.0.1:8081:8080"]
$ docker compose up -d                          # compose.yaml + override (geliştirme)
$ docker compose --profile araclar up -d        # adminer de kalksın
$ docker compose -f compose.yaml -f compose.prod.yaml up -d   # üretim (override okunmaz)

Kararların gerekçesi 7. dersin tablosudur: geliştirmede bind mount (kaynak kod canlı, yeniden derlemeden değişikliği gör), üretimde volume (kalıcı veri) ve kod imajın içinde. Üç pratik tuzak:

  • Dosya olayları. Bind mount üzerinden (özellikle macOS/Windows/WSL2'nin /mnt/c yolunda — 1. ders) dosya değişiklik bildirimi gelmeyebilir; dotnet watch ya da nodemon sessizce uyur. Çözüm yoklama (polling) kipini açmaktır; WSL2'de asıl çözüm projeyi Linux dosya sisteminde tutmaktır.
  • Bağlanan dizin içeriği gizler. ./src:/src bağlarsan imajdaki /src görünmez olur; node_modules klasik kurbandır (çözüm: - /app/node_modules ile anonim volume ile üstünü örtmek).
  • UID. Konteyner root yazarsa hostta root sahipli dosyalar kalır (3. ders). user: "${UID:-1000}:${GID:-1000}" ya da imajda doğru USER.

Compose'un yeni watch desteği bind mount'a alternatiftir: dosya değişince sync (kopyala) ya da rebuild (imajı yeniden derle) yapar — docker compose watch ile çalışır ve bind mount'ın izin/olay sorunlarının çoğunu atlar.

09Ölçekleme, sınırlar ve compose'un bittiği yer

$ docker compose up -d --scale api=3     # aynı servisten üç konteyner

Çalışır — ama iki şartla: o serviste sabit container_name olmamalı ve ports: ile sabit host portu yayımlanmamalıdır (üç konteyner aynı 8080'i alamaz; port aralığı yazılır ya da port hiç yayımlanmaz ve önüne bir ters vekil konur). Compose'un iç DNS'i servis adını üç konteynerin IP'sine çözer, yani ilkel bir yük dağıtımı olur.

Buraya kadar. Compose'un dürüst sınırları şunlardır ve mülakatta bunları bilmek "compose ile üretim yapılır mı" sorusunun cevabıdır:

İhtiyaçComposeKüme (12. ders)
Birden çok makineYok — tek hostZamanlayıcı makineleri yönetir
Kesintisiz güncellemeKendin kurgularsın (11. ders)Rolling update + rollback hazır
Kendini onarma / yeniden zamanlamarestart: kadarDüğüm ölürse başka düğüme taşınır
Otomatik ölçeklemeYokHPA
Kurulum maliyetiBir dosyaKüme işletmek ayrı bir iş

Bu yüzden compose'un iki meşru üretim kullanımı vardır: tek sunuculu küçük dağıtımlar (11. derste göreceğin düzen) ve CI ortamı. Ötesi küme işidir.

10Test ortamı: compose mı, Testcontainers mı

15. hattaki entegrasyon testi dersinin senedi. İkisi de "gerçek veritabanına karşı test" der, ama farklı yerde dururlar:

Compose ile ortamTestcontainers
Yaşam döngüsünü kim yönetirSen / CI adımıTest kodu (başlat, bekle, temizle)
Port çakışmasıSabit portlar çakışabilirRastgele port, paralel koşum güvenli
"Hazır olmayı" kim beklerhealthcheck + --waitKütüphanenin bekleme stratejisi
Artık konteyner riskiVar (down unutulur)Ryuk temizleyici siler
Uygun olduğu yerE2E, çok servisli senaryo, elle keşifOtomatik entegrasyon testleri

Pratik seçim: birim/entegrasyon testleri Testcontainers, uçtan uca ve elle deneme compose. İkisinin de ortak ön şartı aynıdır ve 18. hatta yeniden karşına çıkacak: CI koşucusunda çalışan bir konteyner çalışma zamanı olmalıdır.

11Sık yapılan hatalar

HataBelirtisiDoğrusu
Bağlantı dizesinde localhost"Connection refused" — API kendi içine bakıyorHost=db (servis adı)
Yayımlanan portla bağlanmakServisler arası çağrı çalışmıyorKonteynerin kendi portu (5432)
Yalnız depends_onİlk up'ta API çöküyor, ikincide düzeliyorcondition: service_healthy
down -v refleksiGeliştirme verisi uçtudown; -v bilinçli
Sır environment: içinde düzParola depoda, PR'da, log'da${…:?} + .env (ignore'lu)
Init betiği koşmuyorTablolar yokVolume doluysa çalışmaz; down -v ya da migration
Aynı depoyu iki klasöre klonlamak"Verilerim kayboldu"Proje adı farklı = ayrı volume; name: yaz
version: "3.8" satırıUyarıSil
YAML girintisinde sekmeAyrıştırma hatasıBoşluk; docker compose config ile doğrula

12Mini alıştırma — üç servisli ortamı ayağa kaldır

Yaklaşık 25 dakika · Docker + Compose kurulu ortam

  1. İki servisle başla. Yalnız db (postgres:16) ve cache (redis:7-alpine) olan bir compose.yaml yaz (volume'lerle). up -d, sonra docker compose ps çıktısını kaydet. Oluşan ağ ve volume adlarını docker network ls, docker volume ls ile bul ve proje adı önekini yaz.
  2. DNS'i kanıtla. docker compose exec cache sh -c 'getent hosts db' çalıştır. Sonra docker compose exec cache redis-cli ping ve docker compose exec db pg_isready -U … ile iki servisi de yokla.
  3. API ekle. 8. derste yazdığın Dockerfile ile bir api servisi ekle; bağlantı dizesini Host=db ile ver, ports'u 127.0.0.1:8080:8080 yap. up -d --build sonrası curl -i http://127.0.0.1:8080/health/ready.
  4. Yarış durumunu gör. api'den healthcheck ve service_healthy koşulunu kaldır, down + up yap ve logs api'de bağlantı hatasını yakala. Sonra db'ye pg_isready sağlık kontrolünü ve api'ye condition: service_healthy ekleyip yinele; iki log'u karşılaştır.
  5. Değişken ikamesi. Parolayı ${DB_PAROLA:?parola gerekli} yap. Önce .env olmadan docker compose config çalıştır (hatayı oku), sonra .env ekleyip yinele ve çözülmüş çıktıyı gör. .env'i .gitignore'a ekle.
  6. Kalıcılık. db'de bir tablo oluştur, iki satır ekle. docker compose down + up -d → veri duruyor mu? Sonra docker compose down -v + up -d → ne oldu? İki sonucu yaz.
  7. Override. compose.override.yaml ile db'nin portunu yalnız geliştirmede yayımla ve profiles: ["araclar"] ile bir adminer servisi ekle. up -d ile gelmediğini, --profile araclar up -d ile geldiğini doğrula. docker compose config çıktısında birleşmiş hâli oku.
  8. Ölçekleme sınırı. api için --scale api=3 dene: sabit port yüzünden alacağın hatayı kaydet, ports satırını kaldırıp yinele ve docker compose ps çıktısını yaz.

13Kendini sına

Compose hangi sorunu çözer; docker run ile aynı işi yapmaya göre somut farkları nelerdir? Servisler birbirine hangi adresle bağlanır?

Compose çok servisli bir ortamın tanımını depoya girebilen tek bir dosyaya yazar: hangi imajlar, hangi ortam değişkenleri, hangi volume ve ağ, hangi sırayla. Elle docker run'a göre farkları: tanım sürüm kontrolünde ve PR'da incelenebilir, ağ ve volume kendiliğinden oluşur, down ile hepsi tek komutta kalkar, dosyada değişen servis up -d ile yalnız o servis yeniden oluşturulur ve kurulum dokümanı çalıştırılabilir hâle gelir — yeni katılan biri clone + up ile beş dakikada çalışır duruma gelir. Servisler birbirine servis adıyla bağlanır (Host=db, cache:6379); çünkü Compose proje için kullanıcı tanımlı bir bridge ağı kurar ve orada ad çözümleme çalışır. Konteynerler arası trafikte yayımlanan host portu değil konteynerin kendi portu kullanılır; ports: yalnız hosttan erişim içindir. Konteynerin localhost'u kendisidir, bu yüzden bağlantı dizesinde localhost yazmak tipik hatadır.

depends_on tek başına neyi garanti eder, neyi etmez? healthcheck nasıl yazılır ve hangi ayrıntılara dikkat edilir?

depends_on yalnız başlatma sırasını garanti eder: bağımlı servisin konteyneri önce başlatılır. Ama konteynerin başlaması içindeki servisin hazır olması demek değildir — PostgreSQL ilk açılışta veri dizinini hazırlarken saniyelerce bağlantı kabul etmez, API çöker. Çözüm bağımlı servise healthcheck yazıp depends_on: {db: {condition: service_healthy}} kullanmaktır (diğer koşullar service_started ve tek seferlik işler için service_completed_successfully). Sağlık kontrolünde dikkat edilecekler: servise uygun aracı kullanmak (pg_isready, redis-cli ping, sqlcmd) ve o aracın imajda bulunduğundan emin olmak; boru/koşul gerekiyorsa CMD yerine CMD-SHELL yazmak; start_period vererek açılış süresindeki başarısızlıkların retries sayacını harcamasını önlemek. Kıdemli notu: bu bir geliştirme kolaylığıdır; üretimde bağımlılık her an düşüp kalkabileceği için asıl dayanıklılık uygulamadaki yeniden deneme mantığıyla sağlanır — Kubernetes'te depends_on karşılığı zaten yoktur.

down ile down -v farkı nedir? Compose dosyasındaki ${…} ikamesi ve .env nasıl çalışır; sır nereye yazılır?

down konteynerleri ve proje ağını siler, adlandırılmış volume'lere dokunmaz; down -v volume'leri de siler, yani veritabanı gider — geliştirmede bile refleks hâline getirilmemesi gereken komuttur (bilinçli sıfırlama dışında). İkame: ${AD} yoksa boş dize verir (sessiz arıza), ${AD:-varsayılan} yok ya da boşsa varsayılanı koyar, ${AD:?mesaj} yoksa Compose'u hata ile durdurur — sır için doğru olan budur. Değerler kabuk ortamından, --env-file'dan ya da proje kökündeki .env'den gelir. Karıştırılmaması gereken ayrım: kökteki .env compose dosyasının kendisindeki değişkenleri besler, konteynere kendiliğinden girmez; konteynerin ortamına yazmak için env_file: kullanılır. Sır environment: içine düz yazılmaz; .env (ya da sır yöneticisi) kullanılır, .env .gitignore'a eklenir, depoya değerleri boşaltılmış .env.example konur ve sunucuda dosya izni 640 yapılır. Doğrulama docker compose config ile yapılır: birleşmiş ve çözülmüş son hâli basar.

Geliştirme ve üretim yapılandırmasını compose ile nasıl ayırırsın? Compose'un sınırları nelerdir; üretimde ne zaman yeterli, ne zaman küme gerekir?

Ortak tanım compose.yaml'da durur; geliştirmeye özgü eklemeler compose.override.yaml'a yazılır ve bu dosya bayrak yazmadan kendiliğinden birleşir (bind mount ile canlı kod, dotnet watch, açık veritabanı portu, profiles ile isteğe bağlı araçlar). Üretim için ayrı dosya açıkça verilir: -f compose.yaml -f compose.prod.yaml — böylece override okunmaz. Ayrımın özü 7. dersin tablosudur: geliştirmede bind mount, üretimde volume ve kod imajın içinde. Bind mount'un üç tuzağı: dosya değişiklik olaylarının gelmemesi (yoklama kipi ya da projeyi Linux dosya sisteminde tutmak), bağlanan dizinin imajdaki içeriği gizlemesi ve UID uyuşmazlığı. Compose tek makine aracıdır: çok makineli zamanlama, kendini onarma, otomatik ölçekleme ve hazır rolling update yoktur; --scale ilkel bir kopyalamadır ve sabit host portuyla çalışmaz. Bu yüzden üretimde iki meşru yeri vardır — tek sunuculu küçük dağıtımlar ve CI ortamı; ötesi Kubernetes gibi bir küme işidir. Testte ise otomatik entegrasyon testleri için Testcontainers (rastgele port, kendiliğinden temizlik), uçtan uca/elle deneme için compose tercih edilir.

14Sınav köşesi

Yazılıda nasıl sorulur

(1) docker compose ne işe yarar, docker run'dan farkı nedir? (2) Bir API ve bir PostgreSQL servisini içeren compose.yaml yazınız. (3) Compose'da servisler birbirine hangi adresle erişir? (4) depends_on servisin hazır olmasını garanti eder mi; garanti etmek için ne yapılır? (5) docker compose down ile down -v arasındaki fark nedir? (6) environment ile env_file farkı nedir; sırlar nasıl yönetilir? (7) Geliştirme ve üretim ayarları nasıl ayrılır? (8) volumes bölümünde ad:/yol ile ./yol:/yol arasındaki fark nedir? (9) Bir servisin log'larını ve durumunu hangi komutlarla incelersiniz? (10) Compose üretim için yeterli midir, hangi durumda orkestratör gerekir?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Sorun — "Gerçek uygulama tek konteyner değil: API, PostgreSQL, Redis. Bunları elle docker run ile kaldırmak kişiye bağımlı bir ritüele dönüşür. Compose bu düzeni depoya giren tek bir dosyaya yazar; yeni katılan arkadaş clone + up ile beş dakikada çalışır hâle gelir — kurulum dokümanı çalıştırılabilir olduğu için bayatlamaz." (2) Mekanizma — "Compose proje için bir ağ kurar, servisler birbirini adıyla bulur; bağlantı dizem Host=db'dir, yayımlanan portu değil konteynerin kendi portunu kullanırım. Kalıcı veri adlandırılmış volume'de durur." (3) Senet — "depends_on sadece sıra verir; hazır olmayı garanti etmez. Veritabanına pg_isready ile healthcheck yazar, API'de condition: service_healthy kullanırım. Ama bunun bir geliştirme kolaylığı olduğunu bilirim: üretimde asıl çözüm uygulamanın yeniden deneme mantığıdır." (4) Hijyen — "Sırrı dosyaya düz yazmam; ${DB_PAROLA:?} ile zorunlu kılar, değeri ignore edilmiş .env'den alırım, depoya .env.example koyarım. down -v'yi refleks yapmam, veriyi siler." (5) Kıdem vuruşu — "Geliştirme ve üretimi compose.override.yaml ile ayırırım; geliştirmede bind mount ve watch, üretimde volume. Compose'un tek makine aracı olduğunu bilirim: çok makineli zamanlama, otomatik ölçekleme ve hazır rolling update gerekince Kubernetes'e geçerim; küçük tek sunuculu dağıtımlarda ve CI'da compose bilinçli bir tercihtir."