Healthcheck #

Healthcheck adalah mekanisme untuk menentukan apakah service di dalam container benar-benar sehat — bukan hanya prosesnya jalan, tapi juga siap menerima request dan merespons dengan benar. Tanpa healthcheck, kamu tidak bisa membedakan container yang running tapi broken dari container yang fully functional.

Artikel ini membahas sintaks healthcheck di Compose, cara menulis test command yang efektif, dan best practice untuk health monitoring.

Apa Itu Healthcheck #

Healthcheck adalah perintah yang dijalankan secara periodik di dalam container oleh Docker daemon. Hasil exit code menentukan status:

  • 0 — sehat (healthy)
  • 1 — tidak sehat (unhealthy)
  • 2 — reserved (tidak dipakai)

Status healthcheck divisualisasikan di docker compose ps:

NAME          SERVICE   STATUS              PORTS
myapp-web-1   web       Up 5 minutes (healthy)   0.0.0.0:80->80/tcp
myapp-api-1   api       Up 5 minutes (healthy)   8080/tcp
myapp-db-1    db        Up 5 minutes (healthy)

Jika healthcheck gagal, status menunjukkan (unhealthy). Container belum tentu restart — itu tergantung restart policy.

Sintaks Healthcheck #

Field healthcheck ada di bawah service.

services:
  api:
    image: myapi
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

Field healthcheck #

Field Default Keterangan
test (required) Perintah test
interval 30s Interval antar test
timeout 30s Timeout per test
retries 3 Berapa kali gagal sebelum unhealthy
start_period 0s Grace period di awal

Test Command Formats #

CMD exec form (no shell):

test: ["CMD", "curl", "-f", "http://localhost:8080/health"]

CMD exec form tidak menjalankan shell. Argumen langsung di-pass ke executable. Lebih clean tapi tidak bisa pakai shell features (pipes, env var expansion, dll).

CMD-SHELL form (with shell):

test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]

CMD-SHELL menjalankan perintah di shell (/bin/sh -c). Bisa pakai shell features. Tambah || exit 1 untuk memastikan exit code 1 saat gagal.

NONE (disable):

test: ["NONE"]

Disable inherited healthcheck dari image.

CMD vs CMD-SHELL: Pakai CMD kalau binary test bisa menerima argumen langsung. Pakai CMD-SHELL kalau perlu shell logic (pipes, conditionals, env var). Untuk HTTP check, biasanya CMD-SHELL lebih fleksibel.

Healthcheck per Service Populer #

HTTP Service #

api:
  healthcheck:
    test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
    interval: 30s
    timeout: 10s
    retries: 3

Endpoint /health harus return 200 saat service siap. Bisa lebih spesifik: /health/live (proses jalan) vs /health/ready (siap terima request).

Postgres #

db:
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
    interval: 10s
    timeout: 5s
    retries: 5

pg_isready adalah tool official Postgres untuk cek apakah server siap menerima connection. Return 0 saat ready, 3 saat not ready.

MySQL #

db:
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$MYSQL_ROOT_PASSWORD"]
    interval: 10s
    timeout: 5s
    retries: 5

Redis #

cache:
  healthcheck:
    test: ["CMD", "redis-cli", "ping"]
    interval: 10s
    timeout: 3s
    retries: 3

redis-cli ping return “PONG” (yang di-interpret sebagai exit 0).

MongoDB #

db:
  healthcheck:
    test: ["CMD", "mongo", "--eval", "db.runCommand({ping: 1})"]
    interval: 10s
    timeout: 5s
    retries: 5

RabbitMQ #

mq:
  healthcheck:
    test: ["CMD", "rabbitmq-diagnostics", "ping"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 60s

start_period 60s untuk kasih waktu RabbitMQ boot.

Nginx #

nginx:
  healthcheck:
    test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
    interval: 30s
    timeout: 10s
    retries: 3

Custom Health Endpoint #

Aplikasi yang kamu tulis harus expose endpoint /health yang return 200 saat sehat. Pattern:

# Flask
@app.route('/health')
def health():
    # Cek dependency
    try:
        db.session.execute('SELECT 1')
        return '', 200
    except Exception:
        return 'Database unavailable', 503
// Express
app.get('/health', (req, res) => {
  // Cek dependency
  db.ping()
    .then(() => res.status(200).send('OK'))
    .catch(() => res.status(503).send('DB down'));
});

Start Period untuk Service yang Startup Lambat #

Beberapa service butuh waktu lama untuk fully ready. start_period memberikan grace period di mana healthcheck yang gagal diabaikan.

db:
  image: postgres:16-alpine
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]
    interval: 10s
    timeout: 5s
    retries: 5
    start_period: 60s  # 60s grace period

Dalam 60 detik pertama, healthcheck yang gagal tidak dihitung sebagai unhealthy. Setelah 60s, healthcheck normal.

Penting: start_period tidak menunda healthcheck pertama — ia cuma mengubah kapan counter “unhealthy” mulai dihitung. Healthcheck tetap dijalankan dan mungkin gagal di awal.

Interval dan Retries Tuning #

Default values (interval 30s, retries 3) mungkin tidak optimal untuk semua service.

Service yang butuh quick detection:

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
  interval: 5s
  timeout: 2s
  retries: 2

Detection dalam 5-10s saat service down. Cocok untuk service yang harus segera di-restart saat fail.

Service yang butuh ignore transient failure:

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 5
  start_period: 60s

Detection dalam 2-3 menit, ignore failure singkat. Cocok untuk service yang kadang punya transient blip.

Integration dengan depends_on #

Healthcheck menjadi powerful saat dikombinasikan dengan condition: service_healthy di depends_on.

services:
  api:
    depends_on:
      db:
        condition: service_healthy
      cache:
        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

api tidak start sampai db dan cache keduanya healthy.

Restart Policy dengan Healthcheck #

Saat container unhealthy, restart policy menentukan apa yang terjadi.

services:
  api:
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: on-failure  # atau unless-stopped

restart: on-failure restart container saat exit code non-zero. Healthcheck yang gagal menghasilkan exit code 1 (via || exit 1), jadi container akan restart.

restart: unless-stopped selalu restart kecuali di-stop manual. Ini lebih agresif — container yang “unhealthy” akan di-restart juga.

Anti-Pattern: Healthcheck yang Tidak Berguna #

Healthcheck yang Selalu Pass #

# BURUK
healthcheck:
  test: ["CMD", "true"]  # selalu return 0

Healthcheck harus benar-benar menguji kondisi service.

Healthcheck yang Hanya Cek Proses #

# BURUK
healthcheck:
  test: ["CMD-SHELL", "kill -0 1"]  # cek apakah PID 1 ada

Cek proses jalan ≠ service siap. Web server bisa jalan tapi overloaded; database bisa jalan tapi locked.

Healthcheck yang Terlalu Kompleks #

# BURUK
healthcheck:
  test: ["CMD-SHELL", "complex multi-step check involving many services"]

Healthcheck harus ringan dan cepat. Kalau butuh waktu lama, akan timeout dan selalu fail.

Test Command yang Tidak Ada di Image #

# BURUK — `curl` tidak ada di alpine image
healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost"]

Pastikan command yang dipakai ada di image, atau install di Dockerfile.

Liveness vs Readiness Probe #

Docker healthcheck tunggal berbeda dengan Kubernetes yang punya liveness dan readiness probe terpisah. Tapi konsepnya bisa diadopsi dengan beberapa container per service.

Liveness — apakah proses masih hidup? Restart kalau tidak. Readiness — apakah service siap menerima traffic? Jangan kirim traffic kalau belum siap.

Di Docker Compose, kamu bisa simulasi ini dengan:

services:
  app-liveness:
    image: myapp
    healthcheck:
      test: ["CMD-SHELL", "kill -0 1 || exit 1"]  # proses jalan
      interval: 30s
      retries: 3
    restart: on-failure

  app-readiness:
    image: myapp
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 5s
      retries: 2

Tapi ini bukan pattern yang clean. Untuk liveness + readiness yang proper, lebih baik pakai Kubernetes atau platform yang support kedua probe.

Healthcheck Output Logging #

Saat healthcheck gagal, exit code 1 di-log. Untuk debugging, tambahkan output ke test command.

healthcheck:
  test: ["CMD-SHELL", "curl -v http://localhost:8080/health 2>&1 || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 3

Output verbose bisa dilihat di docker inspect <container> atau docker compose logs <service>.

Custom Healthcheck Script #

Untuk test yang kompleks, tulis script terpisah dan mount ke container.

# healthcheck.sh
#!/bin/sh
set -e

# Cek proses
kill -0 1 || exit 1

# Cek database connection
pg_isready -h db -U app || exit 1

# Cek Redis connection
redis-cli -h cache ping > /dev/null || exit 1

# Cek external API (jika critical)
curl -f https://api.external.com/health > /dev/null 2>&1 || exit 1

exit 0

Mount ke container dan reference di healthcheck:

services:
  app:
    volumes:
      - ./healthcheck.sh:/healthcheck.sh:ro
    healthcheck:
      test: ["CMD-SHELL", "sh /healthcheck.sh"]
      interval: 30s
      timeout: 15s
      retries: 3
Tradeoff custom script: Lebih powerful, bisa test multi-aspek. Tapi lebih kompleks dan bisa fail karena hal eksternal (mis. external API). Untuk production, pertimbangkan apakah dependency eksternal harus masuk healthcheck atau tidak.

Healthcheck dan Observability #

Healthcheck status bisa di-export ke monitoring system.

Built-in Docker metrics:

# Status healthcheck
docker inspect --format='{{.State.Health.Status}}' <container>

# Output terakhir
docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' <container>

Prometheus + cAdvisor:

cAdvisor otomatis export metric container_health ke Prometheus. Alert bisa di-set untuk unhealthy state.

Monitoring eksternal:

Untuk service production, external monitoring (Pingdom, Datadog, dll) melakukan healthcheck dari luar, sehingga bisa detect masalah yang tidak terlihat dari dalam container (network issue, host down, dll).

Per-Container vs External Healthcheck #

Aspek Per-container (Docker) External monitoring
Lokasi Dari dalam container Dari luar host
Detect apa Service internal broken Network, host, atau service
Cost Built-in, gratis Tool tambahan, mungkin berbayar
Reliability Tergantung health endpoint Independen dari service

Untuk production yang serius, gunakan keduanya:

  • Per-container healthcheck untuk depends_on dan restart policy.
  • External monitoring untuk alerting ke tim.

Recap Cheatsheet #

Pattern Contoh
HTTP healthcheck ["CMD-SHELL", "curl -f http://localhost/health || exit 1"]
Postgres ["CMD-SHELL", "pg_isready -U postgres"]
Redis ["CMD", "redis-cli", "ping"]
RabbitMQ ["CMD", "rabbitmq-diagnostics", "ping"]
Generic exec ["CMD", "binary", "arg"]
Disable inherited ["NONE"]
Slow startup start_period: 60s
Quick detection interval: 5s, retries: 2
Tolerate transient interval: 30s, retries: 5

Contoh Lengkap: Production-Ready Healthcheck #

services:
  nginx:
    image: nginx:alpine
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider http://localhost/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    restart: unless-stopped

  api:
    build: ./api
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    volumes:
      - db-data:/var/lib/postgresql/data
    restart: unless-stopped

  cache:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped

volumes:
  db-data:

Stack ini punya healthcheck di setiap service dengan timing yang sesuai. db punya retries lebih banyak (5) karena Postgres kadang butuh waktu lebih lama untuk recovery. api start period 30s untuk warmup. nginx start period 10s saja karena cepat.

Ringkasan #

  • Healthcheck menentukan apakah service siap, bukan hanya prosesnya jalan. Default docker ps tidak bisa membedakan.
  • Sintaks: test: ["CMD-SHELL", "command"] paling fleksibel. CMD exec form untuk binary langsung.
  • Field: test (required), interval, timeout, retries, start_period. Default cocok untuk kebanyakan kasus, tune sesuai kebutuhan.
  • Integration dengan depends_on: pakai condition: service_healthy agar dependent start setelah dependency benar-benar siap.
  • Restart policy: on-failure atau unless-stopped untuk auto-restart saat unhealthy.
  • Anti-pattern: healthcheck yang selalu pass, cek proses saja, terlalu kompleks, atau pakai command yang tidak ada di image.
  • Best practice: health endpoint sederhana di aplikasi (return 200 saat healthy), pakai tool official untuk database (pg_isready, redis-cli ping), tune interval/retries per service.
  • Differentiate liveness vs readiness: liveness = proses jalan (restart kalau down), readiness = siap terima request (jangan traffic).
  • Healthcheck + load balancer = automatic removal dari rotation saat unhealthy (zero manual intervention).
  • Monitor healthcheck failure lewat centralized logging/alerting, jangan andalkan visual check.

← Sebelumnya: Depends On   Berikutnya: Local Development →

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