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 /health atau /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:

  • nginx depends on web
  • web depends on api
  • api depends on db, cache, mq
  • worker depends on api, mq

Semua service punya healthcheck. Container restart policy unless-stopped kecuali yang perlu graceful termination.

Startup order (parallelism dimaksimalkan):

  1. db, cache, mq start paralel
  2. Setelah ketiganya healthy, api dan worker start paralel
  3. Setelah api healthy, web start
  4. Setelah web healthy, nginx start

Total startup time: penjumlahan healthcheck intervals + start_period, bukan penjumlahan dari nol. Compose menunggu sampai semua dependency ready secara paralel.

Ringkasan #

  • depends_on mengatur startup order, tapi default hanya menunggu container start, bukan service siap.
  • Untuk benar-benar menunggu service siap, tambahkan healthcheck di service dependency + condition: service_healthy di depends_on.
  • Healthcheck harus benar-benar menguji apakah service bisa menerima request, bukan hanya apakah prosesnya jalan.
  • Startup orderruntime dependency. Runtime butuh retry logic di aplikasi.
  • Parallel startup — service tanpa depends_on satu 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.

← Sebelumnya: Volume & Network   Berikutnya: Healthcheck →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact