Docker Compose ile çalışırken en çok karşılaşılan sorunlardan biri şu: Uygulamanı başlatıyorsun, her şey ayağa kalkıyor gibi görünüyor ama backend servisi veritabanına bağlanamadığı için hata verip duruyor. Ya da bir message broker tam hazır olmadan consumer servisi başlıyor ve mesajları kaçırıyor. Bu tür problemlerin köküne indiğinde hep aynı şeyi görüyorsun: başlatma sırası yönetimi eksik veya yanlış yapılandırılmış.
Docker Compose’un depends_on direktifi ve condition parametresi bu sorunu çözmek için var. Ama çoğu insan bu mekanizmayı yüzeysel kullanıyor, asıl gücünü keşfetmiyor. Bu yazıda konuyu derinlemesine inceleyeceğiz.
Temel Sorun: Konteyner Başladı, Servis Hazır Değil
Önce bir şeyi netleştirelim. Docker Compose’da bir konteyner “başladı” demek, içindeki servisin kullanıma hazır olduğu anlamına gelmiyor. PostgreSQL konteyneri ayağa kalktığında, veritabanı sürecinin bağlantı kabul etmesi genellikle birkaç saniye daha alır. Redis başladığında AOF dosyasını okuyabilir. Elasticsearch cluster’ının hazır hale gelmesi bazen 30-40 saniye sürebilir.
İşte tam bu noktada naive bir depends_on kullanımı seni yanıltır:
# YANLIS KULLANIM - Sadece konteyner başlamasını bekler, servis hazırlığını değil
services:
web:
image: myapp:latest
depends_on:
- db
db:
image: postgres:15
Bu yapılandırmada web servisi, db konteyneri başlar başlamaz ayağa kalkmaya çalışır. PostgreSQL henüz bağlantı kabul etmeye hazır değilken uygulaman “Connection refused” hatası alır ve çöker. Docker Compose bunu otomatik olarak yeniden başlatmaz (restart policy yoksa).
depends_on ve condition Mekanizması
Docker Compose v2 ile birlikte depends_on direktifi condition parametresi alabilir hale geldi. Bu sayede konteyner başlatma sırası çok daha hassas kontrol edilebilir.
Üç temel condition tipi var:
- service_started: Konteyner başladığında devam et (eski davranış, varsayılan)
- service_healthy: Healthcheck geçene kadar bekle (önerilen yaklaşım)
- service_completed_successfully: Konteyner sıfır çıkış kodu ile bitene kadar bekle (init job’lar için)
Şimdi bunları tek tek inceleyelim.
service_started Condition
En basit kullanım senaryosu. Sadece “bu konteyner başladıktan sonra şunu başlat” demek istiyorsun.
services:
nginx:
image: nginx:alpine
depends_on:
backend:
condition: service_started
ports:
- "80:80"
backend:
image: mybackend:latest
expose:
- "8080"
Bu yaklaşım çok az sayıda senaryoda yeterli. Mesela nginx başlamadan önce backend konteynerinin var olması gerekiyorsa ve nginx kendi içinde bağlantı yönetimini halledebiliyorsa (upstream down olduğunda retry yapıyorsa) işe yarar. Ama çoğu uygulama bu kadar esnek değil.
service_healthy Condition: Gerçek Güç
Bu condition, bir servisin gerçekten “hazır” olduğunu doğrulamak için healthcheck mekanizmasıyla birlikte çalışır. Kullanabilmek için bağımlı olduğun servise bir healthcheck tanımlamalısın.
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: secretpassword
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
interval: 5s
timeout: 5s
retries: 10
start_period: 10s
volumes:
- pgdata:/var/lib/postgresql/data
backend:
image: mybackend:latest
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: "postgresql://appuser:secretpassword@postgres:5432/myapp"
Buradaki healthcheck parametrelerini açalım:
- test: Çalıştırılacak kontrol komutu. CMD-SHELL ile shell üzerinden çalıştırılır
- interval: Her kontrol arasındaki süre (varsayılan 30s)
- timeout: Komutun zaman aşımı süresi
- retries: Kaç kez başarısız olursa “unhealthy” sayılsın
- start_period: Konteyner başladıktan sonra healthcheck başlamadan önce bekleme süresi
start_period parametresi özellikle önemli. Elasticsearch gibi uzun başlangıç süresine sahip servisler için bu değeri yüksek tutmak gerekiyor, aksi halde servis initialize olmadan “unhealthy” sayılıp bağımlı servisler hiç başlamıyor.
Gerçek Dünya Senaryosu 1: Web Uygulaması Stack
Bir Node.js uygulaması, PostgreSQL veritabanı, Redis cache ve Nginx reverse proxy içeren tipik bir stack düşünelim:
version: "3.9"
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ${DB_NAME:-production}
POSTGRES_USER: ${DB_USER:-appuser}
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-appuser} -d ${DB_NAME:-production}"]
interval: 5s
timeout: 3s
retries: 15
start_period: 15s
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 5s
timeout: 3s
retries: 10
start_period: 5s
volumes:
- redis_data:/data
restart: unless-stopped
backend:
image: myapp-backend:${APP_VERSION:-latest}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: "postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}"
REDIS_URL: "redis://:${REDIS_PASSWORD}@redis:6379"
NODE_ENV: production
expose:
- "3000"
restart: unless-stopped
nginx:
image: nginx:alpine
depends_on:
backend:
condition: service_started
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/ssl/certs:ro
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Bu yapılandırmada başlatma sırası şöyle işliyor: Önce postgres ve redis paralel olarak başlıyor. Her ikisi de healthy durumuna geçtiğinde backend başlıyor. Backend konteyneri başladığında nginx devreye giriyor.
service_completed_successfully Condition: Init Job’lar
Bu condition özellikle veritabanı migration’ları veya tek seferlik kurulum görevleri için biçilmiş kaftan.
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
volumes:
- pgdata:/var/lib/postgresql/data
db_migrate:
image: myapp-backend:latest
command: ["npm", "run", "migrate"]
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: "postgresql://appuser:${DB_PASSWORD}@postgres:5432/myapp"
restart: "no"
backend:
image: myapp-backend:latest
depends_on:
db_migrate:
condition: service_completed_successfully
postgres:
condition: service_healthy
environment:
DATABASE_URL: "postgresql://appuser:${DB_PASSWORD}@postgres:5432/myapp"
ports:
- "3000:3000"
restart: unless-stopped
Burada kritik bir detay var: db_migrate servisi için restart: "no" kullanıyoruz. Aksi halde Compose bu servisi sürekli yeniden başlatmaya çalışır. Migration job başarıyla tamamlandıktan sonra backend devreye giriyor.
Gerçek Dünya Senaryosu 2: Microservice Ortamı
Daha karmaşık bir örnek: Bir event-driven microservice mimarisi. RabbitMQ, birden fazla servis ve bir API gateway.
version: "3.9"
services:
rabbitmq:
image: rabbitmq:3.12-management-alpine
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-admin}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS}
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
interval: 10s
timeout: 5s
retries: 20
start_period: 30s
ports:
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
queue_setup:
image: myapp-tools:latest
command: ["python", "setup_queues.py"]
depends_on:
rabbitmq:
condition: service_healthy
environment:
RABBITMQ_URL: "amqp://${RABBITMQ_USER}:${RABBITMQ_PASS}@rabbitmq:5672"
restart: "no"
order_service:
image: order-service:latest
depends_on:
rabbitmq:
condition: service_healthy
queue_setup:
condition: service_completed_successfully
postgres:
condition: service_healthy
environment:
RABBITMQ_URL: "amqp://${RABBITMQ_USER}:${RABBITMQ_PASS}@rabbitmq:5672"
DATABASE_URL: "postgresql://orderuser:${ORDER_DB_PASS}@postgres:5432/orders"
notification_service:
image: notification-service:latest
depends_on:
rabbitmq:
condition: service_healthy
queue_setup:
condition: service_completed_successfully
environment:
RABBITMQ_URL: "amqp://${RABBITMQ_USER}:${RABBITMQ_PASS}@rabbitmq:5672"
SMTP_HOST: ${SMTP_HOST}
api_gateway:
image: api-gateway:latest
depends_on:
order_service:
condition: service_started
notification_service:
condition: service_started
ports:
- "8080:8080"
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: orders
POSTGRES_USER: orderuser
POSTGRES_PASSWORD: ${ORDER_DB_PASS}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U orderuser -d orders"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
rabbitmq_data:
pgdata:
Bu yapıda queue_setup servisi, RabbitMQ üzerinde gerekli exchange ve queue’ları oluşturuyor. Sadece bu tamamlandıktan sonra consumer servisler başlıyor.
Healthcheck Yazarken Dikkat Edilmesi Gerekenler
İyi bir healthcheck yazmak düşündüğünden biraz daha nüanslı.
# PostgreSQL için - Sadece sürecin çalışmasını değil, bağlantı kabul etmesini kontrol et
pg_isready -U username -d dbname -h localhost
# MySQL/MariaDB için
mysqladmin ping -h localhost -u root -p${MYSQL_ROOT_PASSWORD}
# Redis için (şifre varsa)
redis-cli -a ${REDIS_PASSWORD} ping
# MongoDB için
mongosh --eval "db.adminCommand('ping')" --quiet
# HTTP servisi için - HTTP status kodu kontrol
curl -f http://localhost:8080/health || exit 1
# Daha detaylı HTTP kontrol - Response body da kontrol et
curl -sf http://localhost:8080/health | grep -q '"status":"ok"' || exit 1
Healthcheck yazarken kaçınılması gereken bazı yaygın hatalar var:
- Sadece process varlığını kontrol etme:
pgrep postgresveyaps aux | grep redisgibi kontroller yanıltıcı. Process çalışıyor olabilir ama servis henüz request kabul etmiyor olabilir. - Çok agresif interval: 1-2 saniyelik interval hem kaynakları tüketir hem de log’ları kirletir. 5-10 saniye genellikle makul.
- start_period’ı atlamak: Özellikle JVM tabanlı uygulamalar (Elasticsearch, Kafka) için start_period kritik. Bu süre zarfında başarısız healthcheck’ler retry sayacını etkilemez.
Elasticsearch için Özel Durum
Elasticsearch başlatma sırası yönetiminde en zorlu servislerden biri. Hem single-node hem cluster modda sağlıklı bir healthcheck örneği:
services:
elasticsearch:
image: elasticsearch:8.10.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf http://localhost:9200/_cluster/health | grep -qE '"status":"(green|yellow)"'"
]
interval: 15s
timeout: 10s
retries: 20
start_period: 60s
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata:/usr/share/elasticsearch/data
kibana:
image: kibana:8.10.0
depends_on:
elasticsearch:
condition: service_healthy
environment:
ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
ports:
- "5601:5601"
volumes:
esdata:
60 saniyelik start_period abartılı gibi görünebilir ama Elasticsearch ilk kez başladığında veya indeks recovery yapıyorsa bu süre çok rahat dolabilir. Bunu kısaltırsan false-unhealthy durumlarıyla karşılaşabilirsin.
Compose’u Komut Satırından Debug Etmek
Başlatma sorunlarını debug ederken birkaç komut hayat kurtarıyor:
# Servislerin durumunu ve healthcheck bilgisini görüntüle
docker compose ps
# Belirli bir servisin healthcheck geçmişini gör
docker inspect myapp-postgres-1 | grep -A 20 '"Health"'
# Servisleri dependency sırasına göre göster
docker compose config --services
# Belirli bir servisi bağımlılıklarıyla birlikte başlat ve logları takip et
docker compose up --wait backend
# Sadece dependency chain'ini başlat, hedef servisi değil
docker compose up -d postgres redis
# Healthcheck loglarını gerçek zamanlı izle
watch -n 2 'docker compose ps'
--wait flag’i özellikle CI/CD pipeline’larında çok işe yarıyor. Bu flag ile docker compose up komutu, tüm servisler healthy durumuna gelene kadar bekliyor ve sonra çıkıyor.
# CI/CD'de kullanım - Tüm servisler healthy olana kadar bekle, timeout 120 saniye
docker compose up -d --wait --wait-timeout 120
# Çıkış kodunu kontrol et
if [ $? -ne 0 ]; then
echo "Servisler başlatılamadı!"
docker compose logs
exit 1
fi
Döngüsel Bağımlılık Problemi
Bazen servisler birbirlerine bağımlı hale gelebilir. Bu durumu Compose algılar ve hata verir, ama bu hatayı görünce ne yapman gerektiğini bilmek lazım.
# BU HATALI - Döngüsel bağımlılık oluşturur
services:
service_a:
depends_on:
service_b:
condition: service_healthy
service_b:
depends_on:
service_a:
condition: service_healthy
Eğer gerçekten iki servisin birbirini beklemesi gerekiyorsa (ki bu genellikle mimari problemine işaret eder), çözüm yolu bir ara servis veya sidecar pattern kullanmak, ya da her iki servisin de bağımsız başlayıp kendi içinde retry mekanizması uygulaması.
profiles ile Seçici Başlatma
profiles özelliğiyle birlikte depends_on kullanımında dikkatli olmak gerekiyor. Bir servisin profile’ı aktif değilse, ona bağımlı olan servisler başlatılamaz.
services:
backend:
image: myapp:latest
profiles:
- production
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:15
profiles:
- production
- development
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
dev_tools:
image: pgadmin:latest
profiles:
- development
depends_on:
postgres:
condition: service_healthy
# Sadece production profile'ını aktifleştir
docker compose --profile production up -d
# Sadece development profile'ını aktifleştir
docker compose --profile development up -d
Yaygın Hatalar ve Çözümleri
Pratikte sık karşılaştığım sorunları listeleyelim:
- “dependency failed to start”: Bağımlı servisin healthcheck’i başarısız.
docker compose logsile servis loglarına bak,docker inspectile healthcheck detaylarını incele.
- Sonsuz bekleme:
retriesdeğeri çok yüksek veya healthcheck komutu daima başarısız oluyordur.retries: 5ile başla, production’da ihtiyaca göre artır.
- “no such service” hatası:
depends_oniçinde yazım hatası. Servis isimlerinde büyük-küçük harf duyarlılığı var.
- Healthcheck geçiyor ama uygulama hala hazır değil: Healthcheck’in gerçekten uygulama katmanını test etmesi gerekiyor. Sadece port dinleniyor mu kontrol etmek yeterli değil, gerçek bir request at.
- Windows’ta path problemi: Healthcheck komutlarında Windows line endings (CRLF) sorun yaratabilir, özellikle script dosyaları kullanıyorsan.
Sonuç
depends_on ve condition mekanizması, Docker Compose’un en çok değeri bilinmeyen özelliklerinden biri. Basit görünüyor ama doğru kullanmak gerçekten çevre stabilitesini ciddi ölçüde artırıyor.
Özetlemek gerekirse:
- service_started: Konteyner sıralamak için yeterli, gerçek hazırlık kontrolü yok
- service_healthy: Production ortamları için standart olmalı, healthcheck tanımı zorunlu
- service_completed_successfully: Migration’lar ve init job’lar için ideal
Her yeni Docker Compose stack’i oluştururken şu soruyu sor: “Bu servis gerçekten hazır olmadan diğeri başlarsa ne olur?” Cevap “hata verir veya yanlış davranır” ise service_healthy condition ve düzgün bir healthcheck ekle.
En başta basit bir şey gibi görünen başlatma sırası yönetimi, production ortamında binlerce satır loglama, anlamsız restart döngüleri ve saatler süren debug seanslarını ortadan kaldırıyor. Bu yatırımı yapmaya değer.