Depends On & Startup Order #
Saat kamu punya banyak service, urutan startup menjadi penting. Database harus siap sebelum aplikasi coba konek. Cache harus ready sebelum worker mulai pull message. Docker Compose menyediakan field depends_on untuk mengatur urutan ini.
Tapi depends_on punya keterbatasan yang sering bikin bug: ia hanya menunggu container start, bukan service siap menerima request. Untuk benar-benar menunggu service siap, kamu butuh kombinasi healthcheck + condition.
Artikel ini membahas semua itu secara mendalam, lengkap dengan pola dependency yang umum.
depends_on: Sintaks Dasar #
Field depends_on ada di bawah service dan berisi list service lain yang harus start duluan.
services:
web:
depends_on:
- api
api:
depends_on:
- db
- cache
db:
image: postgres:16-alpine
cache:
image: redis:7-alpine
Pada contoh di atas, db dan cache start duluan (paralel), lalu api start, lalu web. Urutan ini deterministik.
depends_on juga bisa ditulis dengan map syntax untuk lebih eksplisit:
services:
api:
depends_on:
db:
condition: service_started
cache:
condition: service_started
condition bisa berupa:
service_started(default) — container sudah start, tapi belum tentu siap.service_healthy— healthcheck pass.service_completed_successfully— container exit dengan code 0 (untuk one-off jobs).
Masalah: depends_on Tidak Menunggu Service Siap #
Kelemahan terbesar depends_on default adalah ia hanya menunggu container start, bukan service siap. Untuk service yang butuh inisialisasi panjang (database yang harus load schema, JVM warm-up, lazy initialization), depends_on saja tidak cukup.
Contoh masalah:
services:
api:
build: ./api
depends_on:
- db
db:
image: postgres:16-alpine
# Database butuh beberapa detik untuk siap menerima connection
Saat docker compose up, container db start (proses postgres dimulai). Compose langsung start api. Tapi postgres mungkin belum siap menerima connection — database belum initialize cluster, belum ready untuk query. API coba konek, gagal, crash.
Ini bug yang sangat umum di Docker Compose pemula.
Solusi: Healthcheck + Condition: service_healthy #
Untuk benar-benar menunggu service siap, tambahkan healthcheck ke service yang jadi dependency, lalu gunakan condition: service_healthy.
services:
api:
build: ./api
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
api sekarang tidak start sampai healthcheck db pass. Postgres siap menerima connection = API bisa start dengan aman.
Healthcheck di Docker Compose #
Healthcheck adalah perintah yang dijalankan periodik di dalam container untuk menentukan apakah service sehat.
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"] # perintah test
interval: 10s # setiap berapa detik
timeout: 5s # timeout per test
retries: 5 # berapa kali gagal sebelum unhealthy
start_period: 30s # grace period di awal
Field healthcheck:
| Field | Default | Keterangan |
|---|---|---|
test |
(required) | Perintah untuk cek kesehatan |
interval |
30s | Interval antar test |
timeout |
30s | Timeout per test |
retries |
3 | Berapa kali gagal sebelum dianggap unhealthy |
start_period |
0s | Grace period di awal (test gagal diabaikan) |
test bisa berupa:
["CMD", "command", "arg1", "arg2"]— exec form["CMD-SHELL", "shell command"]— shell form["NONE"]— disable inherited healthcheck dari image
Contoh Healthcheck per Service #
Postgres:
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 10s
timeout: 5s
retries: 5
Redis:
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
MySQL:
db:
image: mysql:8
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
MongoDB:
db:
image: mongo:7
healthcheck:
test: ["CMD", "mongo", "--eval", "db.runCommand({ping: 1})"]
interval: 10s
timeout: 5s
retries: 5
RabbitMQ:
mq:
image: rabbitmq:3-management
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
Custom HTTP API:
api:
build: .
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
Healthcheck yang baik menguji apakah service bisa menerima request dari external client, bukan hanya apakah prosesnya jalan. Untuk web service, cek endpoint/healthatau/ready. Untuk database, cek apakah bisa menerima query.
Pola Dependency Umum #
API + Database #
Pola paling klasik:
services:
api:
build: ./api
depends_on:
db:
condition: service_healthy
environment:
- DATABASE_URL=postgres://app:secret@db:5432/myapp
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=app
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Frontend + API + Multiple Backend #
services:
web:
image: nginx
depends_on:
- api
api:
build: ./api
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
mq:
condition: service_healthy
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
mq:
image: rabbitmq:3-management
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
Worker + Queue #
services:
worker:
build: ./worker
depends_on:
rabbitmq:
condition: service_healthy
redis:
condition: service_healthy
environment:
- QUEUE_URL=amqp://guest:guest@rabbitmq:5672
- REDIS_URL=redis://redis:6379
rabbitmq:
image: rabbitmq:3-management
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
Init Container Pattern #
Untuk setup data sebelum service utama start, gunakan init container:
services:
app:
build: .
depends_on:
init:
condition: service_completed_successfully
volumes:
- data:/app/data
init:
image: alpine
volumes:
- data:/app/data
command: sh -c "echo '$(date)' > /app/data/init.txt && echo 'init complete'"
restart: "no"
init container jalan sekali, exit. app start setelah init exit dengan code 0.
Startup Order vs Runtime #
depends_on mengatur startup order, bukan runtime behavior. Setelah semua service jalan, dependensi di antara mereka di-handle oleh protokol aplikasi itu sendiri (retry, circuit breaker, dll).
Contoh: api depends on db di startup, tapi saat runtime, api harus handle db restart atau temporary unavailable. Healthcheck membantu, tapi retry logic di aplikasi juga penting.
# Contoh retry logic di Python
import time
import psycopg2
def connect_with_retry(max_retries=5, delay=2):
for attempt in range(max_retries):
try:
return psycopg2.connect(DATABASE_URL)
except psycopg2.OperationalError:
if attempt < max_retries - 1:
time.sleep(delay)
delay *= 2 # exponential backoff
else:
raise
Parallel Startup #
Service yang tidak punya depends_on satu sama lain start secara paralel. Ini bagus untuk performa.
services:
cache:
image: redis:7-alpine
mq:
image: rabbitmq:3-management
db:
image: postgres:16-alpine
cache, mq, dan db di atas start paralel karena tidak saling depend.
Tapi service yang punya depends_on di chain yang sama start sequential:
services:
web: # start ke-4
depends_on: [api]
api: # start ke-3
depends_on: [db, cache]
db: # start ke-1
image: postgres
cache: # start ke-2 (paralel dengan db)
image: redis
Docker Compose mencoba start sebanyak mungkin service secara paralel sambil menghormati dependency.
Best Practice #
Selalu Gunakan Healthcheck untuk Dependency #
Untuk service yang butuh dependency, selalu tambahkan healthcheck. Tanpa healthcheck, depends_on saja unreliable.
# WAJIB untuk service yang punya dependents
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
Tune Healthcheck Interval dan Retries #
Default values (30s interval, 3 retries) mungkin terlalu lambat atau cepat untuk kasus-mu.
- Service yang startup cepat (Redis, nginx): interval pendek (5-10s), retries rendah (3).
- Service yang startup lambat (database besar, JVM): interval panjang (30s+), retries tinggi (5-10), start_period panjang (30-60s).
Hindari Circular Dependency #
Service A depends on B, B depends on A → circular dependency → docker compose error.
Pisahkan dependency chain.
# HINDARI
services:
api:
depends_on: [worker]
worker:
depends_on: [api]
# BENAR
services:
api:
depends_on: [queue]
worker:
depends_on: [queue]
Gunakan Restart Policy untuk Resilience #
Untuk service yang restart-able, set restart: on-failure atau unless-stopped. Container yang crash akan di-restart otomatis.
services:
worker:
restart: unless-stopped
depends_on:
queue:
condition: service_healthy
Logging untuk Debug #
Saat depends_on tidak bekerja seperti yang diharapkan, cek log service yang dependent.
docker compose logs api
docker compose logs --tail 100 web
Error “connection refused” atau “no such host” biasanya karena dependent start sebelum dependency siap.
Recap Patterns #
Pilih pattern berdasarkan kompleksitas dependency:
| Pattern | Gunakan ketika |
|---|---|
depends_on: [service] |
Service dependent bisa retry, dependency start cepat |
depends_on: { x: { condition: service_healthy } } |
Service dependent harus tunggu dependency siap |
| Init container | Butuh setup data sebelum service utama |
No depends_on |
Service independent, tidak butuh ordering |
Common Pitfalls #
1. Lupa Healthcheck #
Paling umum. Tanpa healthcheck, condition: service_healthy tidak ada artinya.
# SALAH
services:
api:
depends_on:
db:
condition: service_healthy # tidak ada gunanya tanpa healthcheck
db:
image: postgres
# healthcheck missing
2. Healthcheck Terlalu Ketat atau Terlalu Longgar #
Terlalu ketat (test yang gagal di awal): container restart loop. Terlalu longgar (test selalu pass): unhealthy container tidak terdeteksi.
Tune dengan start_period untuk grace period.
3. Asumsi depends_on Berarti Runtime Dependency
#
depends_on hanya untuk startup. Runtime butuh handling lain (retry, circuit breaker).
4. Tidak Test Restart Scenario #
docker compose up sekali jalan mudah. Test juga: stop db setelah stack jalan, lalu start lagi. Apakah api reconnect dengan benar?
Contoh Lengkap: Microservice Stack #
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
depends_on:
web:
condition: service_healthy
networks:
- frontend
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
web:
build: ./web
depends_on:
api:
condition: service_healthy
networks:
- frontend
- backend
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
api:
build: ./api
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
mq:
condition: service_healthy
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
worker:
build: ./worker
depends_on:
api:
condition: service_healthy
mq:
condition: service_healthy
networks:
- backend
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=app
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=myapp
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
restart: unless-stopped
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
networks:
- backend
restart: unless-stopped
mq:
image: rabbitmq:3-management
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- backend
restart: unless-stopped
volumes:
db-data:
networks:
frontend:
backend:
Stack ini punya 7 service dengan dependency:
nginxdepends onwebwebdepends onapiapidepends ondb,cache,mqworkerdepends onapi,mq
Semua service punya healthcheck. Container restart policy unless-stopped kecuali yang perlu graceful termination.
Startup order (parallelism dimaksimalkan):
db,cache,mqstart paralel- Setelah ketiganya healthy,
apidanworkerstart paralel - Setelah
apihealthy,webstart - Setelah
webhealthy,nginxstart
Total startup time: penjumlahan healthcheck intervals + start_period, bukan penjumlahan dari nol. Compose menunggu sampai semua dependency ready secara paralel.
Ringkasan #
depends_onmengatur startup order, tapi default hanya menunggu container start, bukan service siap.- Untuk benar-benar menunggu service siap, tambahkan healthcheck di service dependency +
condition: service_healthydidepends_on.- Healthcheck harus benar-benar menguji apakah service bisa menerima request, bukan hanya apakah prosesnya jalan.
- Startup order ≠ runtime dependency. Runtime butuh retry logic di aplikasi.
- Parallel startup — service tanpa
depends_onsatu sama lain start paralel. Compose memaksimalkan paralelisme sambil menghormati chain.- Avoid circular dependency — pisahkan chain dependency.
- Tune healthcheck per service: interval/retries sesuai karakteristik service (startup time, response time).
- Restart policy untuk resilience — service yang crash akan auto-restart, asal dependency masih sehat.
- Debugging dengan
docker compose logs— error seperti “connection refused” atau “no such host” biasanya dependent start sebelum dependency siap.- Init container untuk setup data sebelum service utama start, dengan
condition: service_completed_successfully.