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.yamlanatomisi: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 .envve 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 run | Compose |
|---|---|
| Komutlar kafada / kişisel notlarda | Dosya depoda, sürüm kontrolünde, PR'da incelenir |
| Ağ ve volume'ü elle kurmayı hatırlaman gerekir | Proje için ağ ve volume kendiliğinden oluşur |
Kaldırma: her konteyneri tek tek rm | docker compose down — hepsi, ağ dâhil |
| Bir servisin ayarını değiştirmek = komutu yeniden yazmak | Dosyayı 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).
| Anahtar | docker run karşılığı | Dikkat |
|---|---|---|
image: postgres:16 | imaj adı | Hazır imaj çekilir; etiket yazılır |
build: . / build: {context, dockerfile, target, args} | docker build | Kendi imajını derler; image: ile birlikte yazılırsa üretilen imaja o ad verilir |
ports: ["127.0.0.1:8080:8080"] | -p | Yalnız hosttan erişim için; servisler arası trafikte gerekmez |
expose: ["8080"] | EXPOSE | Yalnız beyan; port yayımlamaz |
environment: | -e | Liste ya da eşleme yazımı; sır yazılmaz |
env_file: [.env.local] | --env-file | Sırlar buraya; dosya .gitignore'da |
volumes: | -v | ad:/yol volume, ./yol:/yol bind mount |
networks: | --network | Yazmazsan 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: | HEALTHCHECK | Servisin hazır olduğunu Compose'a bildirir |
restart: unless-stopped | --restart | 7. dersin politika tablosu aynen geçerli |
command: / entrypoint: | imajdan sonraki komut | İmajın CMD/ENTRYPOINT'ini ezer |
user: "10001:10001" | --user | Köksüz çalıştırma (3. ders senedi) |
deploy.resources.limits | --memory, --cpus | Compose 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_defaultağı). 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ğeriappsettings.json'dakiConnectionStrings:Basvurualanı 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/.shdosyaları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:
- Doğru aracı kullan. PostgreSQL için
pg_isready, Redis içinredis-cli ping, SQL Server içinsqlcmd -Q "SELECT 1", kendi API'n için hazır olma ucu.curl'ün imajda bulunduğunu varsayma —postgresimajındacurlyoktur. CMD↔CMD-SHELL.CMDlisteyi doğrudan çalıştırır; boru,||, değişken genişletmesi gerekiyorsaCMD-SHELLile tek dize yazılır.start_periodver. Bu süre içindeki başarısız denemelerretriessayacı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. derstekiprune --volumesuyarısının compose hâli. Veriyi bilerek sıfırlamak istediğinde ise doğru komut budur ("şema bozuldu, temiz başla").configteşhisin ilk adımıdır: override dosyaları,.envikamesi 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:execzaten çalışan konteynere girer;runyeni bir konteyner başlatır (migration, tek seferlik betik).--rmile çöp bırakmaz,--no-depsile bağımlılıkları başlatmaz.- Birden çok dosya:
docker compose -f compose.yaml -f compose.prod.yaml up -dile dosyalar birleştirilir (sonraki öncekini ezer);-pile proje adı değiştirilerek aynı depodan iki bağımsız ortam (ör.testvedemo) 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ım | Anlamı |
|---|---|
${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
.envdosyası 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/cyolunda — 1. ders) dosya değişiklik bildirimi gelmeyebilir;dotnet watchya danodemonsessizce 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:/srcbağlarsan imajdaki/srcgörünmez olur;node_modulesklasik kurbandır (çözüm:- /app/node_modulesile 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ğruUSER.
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ç | Compose | Küme (12. ders) |
|---|---|---|
| Birden çok makine | Yok — tek host | Zamanlayıcı makineleri yönetir |
| Kesintisiz güncelleme | Kendin kurgularsın (11. ders) | Rolling update + rollback hazır |
| Kendini onarma / yeniden zamanlama | restart: kadar | Düğüm ölürse başka düğüme taşınır |
| Otomatik ölçekleme | Yok | HPA |
| Kurulum maliyeti | Bir dosya | Kü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 ortam | Testcontainers | |
|---|---|---|
| Yaşam döngüsünü kim yönetir | Sen / CI adımı | Test kodu (başlat, bekle, temizle) |
| Port çakışması | Sabit portlar çakışabilir | Rastgele port, paralel koşum güvenli |
| "Hazır olmayı" kim bekler | healthcheck + --wait | Kütüphanenin bekleme stratejisi |
| Artık konteyner riski | Var (down unutulur) | Ryuk temizleyici siler |
| Uygun olduğu yer | E2E, çok servisli senaryo, elle keşif | Otomatik 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
| Hata | Belirtisi | Doğrusu |
|---|---|---|
Bağlantı dizesinde localhost | "Connection refused" — API kendi içine bakıyor | Host=db (servis adı) |
| Yayımlanan portla bağlanmak | Servisler arası çağrı çalışmıyor | Konteynerin kendi portu (5432) |
Yalnız depends_on | İlk up'ta API çöküyor, ikincide düzeliyor | condition: service_healthy |
down -v refleksi | Geliştirme verisi uçtu | down; -v bilinçli |
Sır environment: içinde düz | Parola depoda, PR'da, log'da | ${…:?} + .env (ignore'lu) |
| Init betiği koşmuyor | Tablolar yok | Volume 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 sekme | Ayrış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
- İki servisle başla. Yalnız
db(postgres:16) vecache(redis:7-alpine) olan bircompose.yamlyaz (volume'lerle).up -d, sonradocker compose psçıktısını kaydet. Oluşan ağ ve volume adlarınıdocker network ls,docker volume lsile bul ve proje adı önekini yaz. - DNS'i kanıtla.
docker compose exec cache sh -c 'getent hosts db'çalıştır. Sonradocker compose exec cache redis-cli pingvedocker compose exec db pg_isready -U …ile iki servisi de yokla. - API ekle. 8. derste yazdığın Dockerfile ile bir
apiservisi ekle; bağlantı dizesiniHost=dbile ver,ports'u127.0.0.1:8080:8080yap.up -d --buildsonrasıcurl -i http://127.0.0.1:8080/health/ready. - Yarış durumunu gör.
api'denhealthcheckveservice_healthykoşulunu kaldır,down+upyap velogs api'de bağlantı hatasını yakala. Sonradb'yepg_isreadysağlık kontrolünü veapi'yecondition: service_healthyekleyip yinele; iki log'u karşılaştır. - Değişken ikamesi. Parolayı
${DB_PAROLA:?parola gerekli}yap. Önce.envolmadandocker compose configçalıştır (hatayı oku), sonra.envekleyip yinele ve çözülmüş çıktıyı gör..env'i.gitignore'a ekle. - Kalıcılık.
db'de bir tablo oluştur, iki satır ekle.docker compose down+up -d→ veri duruyor mu? Sonradocker compose down -v+up -d→ ne oldu? İki sonucu yaz. - Override.
compose.override.yamliledb'nin portunu yalnız geliştirmede yayımla veprofiles: ["araclar"]ile biradminerservisi ekle.up -dile gelmediğini,--profile araclar up -dile geldiğini doğrula.docker compose configçıktısında birleşmiş hâli oku. - Ölçekleme sınırı.
apiiçin--scale api=3dene: sabit port yüzünden alacağın hatayı kaydet,portssatırını kaldırıp yinele vedocker 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."
