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: PakaiCMDkalau binary test bisa menerima argumen langsung. PakaiCMD-SHELLkalau perlu shell logic (pipes, conditionals, env var). Untuk HTTP check, biasanyaCMD-SHELLlebih 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_ondan 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 pstidak bisa membedakan.- Sintaks:
test: ["CMD-SHELL", "command"]paling fleksibel.CMDexec 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_healthyagar dependent start setelah dependency benar-benar siap.- Restart policy:
on-failureatauunless-stoppeduntuk 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.