Gerbang service_healthy akhirnya membuat web menyala hanya setelah db sanggup menerima koneksi — tetapi seluruh bangunan itu bertumpu pada satu benda kecil yang belum pernah kita periksa kelayakannya: probe pg_isready itu sendiri. Healthcheck yang salah rancang membuat gerbang bohong besar; probe yang terlalu longgar meloloskan service yang sebenarnya sakit, sedangkan probe yang terlalu ketat menggantungkan stack pada service yang sehat. Chapter ini membedah cara mendesain probe yang jujur di dalam compose.yaml — dari pemilihan uji yang mengarah ke fungsi layanan, tata tulis CMD dan CMD-SHELL, nilai bawaan yang jarang cocok dipakai apa adanya, sampai siklus status yang dialami setiap container ber-healthcheck. Di akhir chapter, berkas katalog terkumpul lengkap untuk pertama kalinya: volume, environment, urutan start, dan dua healthcheck yang saling menjaga.

Tiga Alasan Healthcheck Penting di Compose #

Instruksi HEALTHCHECK pada Dockerfile — yang kita bedah pada Chapter HEALTHCHECK — menanam probe ke dalam image, sehingga setiap container dari image itu diperiksa dengan cara yang sama. Kunci healthcheck di compose.yaml memindahkan keputusan itu ke tangan operator: probe dideklarasikan per service, tanpa membongkar dan membangun ulang image pihak ketiga — persis kebutuhan kita terhadap postgres:18-alpine yang tidak bisa kita sunting Dockerfile-nya.

Tiga peran membuat healthcheck layak diperlakukan serius di tingkat Compose:

  1. Status yang jujur di docker compose ps — kolom status menampilkan (healthy) atau (unhealthy) di samping Up; operator melihat kesehatan stack dalam satu pandangan, bukan hanya napas prosesnya.
  2. Gerbang depends_on — kondisi service_healthy dari chapter sebelumnya sama sekali tidak bermakna tanpa probe yang layak; kualitas gerbang = kualitas healthcheck.
  3. Bahasa diagnosa bersama — status kesehatan dan log probe menjadi titik berangkat yang sama bagi seluruh tim saat membongkar insiden.

Probe yang Benar: Uji Fungsi, Bukan Prosesnya #

Probe paling banyak salah rancang adalah yang menguji keberadaan proses. pgrep postgres yang berhasil keluar exit 0 hanya membuktikan bahwa proses bernama postgres hidup — padahal server yang masih menjalankan initdb, atau yang menggantung tanpa menjawab, sama-sama punya proses yang hidup. Probe yang jujur menguji jalur yang benar-benar dipakai pengguna layanan.

Untuk web, uji fungsi yang jujur adalah endpoint /healthz yang mengetuk database sungguhan:

js
app.get("/healthz", async (_req, res) => {
  await pool.query("SELECT 1");
  res.sendStatus(200);
});

Mari kita telaah tiga baris rute di atas:

  1. pool.query("SELECT 1") — permintaan termurah yang tetap menempuh jalur lengkap: koneksi ke db, pengiriman kueri, dan pengembalian jawaban; bila database tak terjangkau, baris ini melempar galat sebelum baris berikutnya sempat jalan.
  2. res.sendStatus(200) — hanya terkirim bila kueri berhasil; handler galat Express menjawab 500 untuk keadaan sebaliknya.
  3. Jawaban 200 versus 500 — inilah protokol kejujuran probe: status HTTP dipetakan langsung ke exit code oleh alat uji, sehingga web dinyatakan sehat hanya bila seluruh rantai web → network → db benar-benar berfungsi.

Blok healthcheck untuk Service web #

Image node:22-alpine berdiri di atas Alpine yang membawa busybox, dan busybox menyertakan wget — kita tidak perlu memasang alat apa pun. Probe untuk endpoint di atas berbentuk:

yaml
  web:
    build: .
    ports:
      - "8080:3000"
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/healthz"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 15s
      start_interval: 5s

Mari kita telaah blok di atas kunci demi kunci:

  1. test dengan bentuk CMD — bentuk exec tanpa shell: daemon menjalankan wget langsung sebagai proses baru dengan argumen yang tertulis, tanpa perantara /bin/sh.
  2. wget --spider -q — --spider hanya mengetuk halaman tanpa mengunduh isinya, dan -q menjaga probe hening; exit code wget menjadi bahasa statusnya: 0 bila jawaban 2xx, selain itu gagal.
  3. interval: 10s dan timeout: 3s — pemeriksaan rutin tiap 10 detik dengan batas 3 detik per percobaan; probe yang menggantung cepat terbaca sebagai kegagalan.
  4. retries: 3 — tiga kegagalan beruntun mengubah status menjadi unhealthy; satu-dua gangguan sesaat tidak membuat status bergolak.
  5. start_period: 15s dan start_interval: 5s — Node.js butuh waktu membuka koneksi pool sebelum /healthz bisa menjawab; masa hangar 15 detik memberi ruang itu, dengan probe dipercepat menjadi tiap 5 detik agar status healthy tercapai segera setelah aplikasi siap.

CMD versus CMD-SHELL #

Bentuk CMD tidak mengenal fitur shell: tidak ada ||, tidak ada pemipaan, tidak ada penggantian variabel. Begitu salah satu fitur itu dibutuhkan, gunakan CMD-SHELL — string yang dijalankan melalui shell container. Healthcheck db dari chapter sebelumnya bisa kita rapikan dengan memanfaatkan variabel yang sudah ada di environment:

yaml
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]

Mari kita telaah dua hal penting pada baris ini:

  1. $$ bukan salah tik — Compose membaca ${...} pada nilai YAML sebagai tanda interpolasi; untuk meneruskan tanda $ literal kepada shell di dalam container, ia harus ditulis dua kali. Setelah interpolasi Compose selesai, shell container menerima pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} dan menggantikan variabelnya dari environment container.
  2. Kredensial tak lagi ditulis dua kali — nilai katalog hidup satu sumber di blok environment; mengubahnya di satu tempat otomatis mengubah perilaku probe.

Nilai Bawaan yang Jarang Cocok #

Kunci-kunci healthcheck punya nilai bawaan yang sebaiknya dihafal — dan diwaspadai:

KunciPeranNilai bawaan
intervalJeda antar pemeriksaan rutin30s
timeoutBatas waktu tiap probe30s
retriesKegagalan beruntun sebelum unhealthy3
start_periodMasa hangar — kegagalan tak dihitung0s
start_intervalJeda khusus selama masa hangarmengikuti interval

Dua nilai bawaan itu paling sering menipu. timeout 30 detik membuat probe yang menggantung baru dinyatakan gagal setelah setengah menit — kita sudah mencatat kejutan yang sama pada Chapter HEALTHCHECK, dan hampir semua stack menuliskan nilai yang jauh lebih ketat. start_period 0s berarti tidak ada masa hangar sama sekali: aplikasi yang butuh lima detik untuk bangun langsung dihitung gagal sejak probe pertama. Nilai bawaan memang aman untuk probe kilat, tetapi jarang cocok untuk aplikasi sungguhan.

Siklus Status: starting, healthy, unhealthy #

Setiap container ber-healthcheck menempuh siklus status yang bisa diprediksi. Diagram berikut memetakan perpindahannya:

Mari kita telaah siklus pada diagram di atas:

  1. health: starting — status awal sejak container menyala; kegagalan probe selama start_period tidak menambah penghitung kegagalan, itulah makna masa grasi.
  2. healthy — dicapai begitu probe lulus pertama kali; penghitung kegagalan beruntun dikembalikan ke nol.
  3. unhealthy — kegagalan beruntun sebanyak retries menurunkan status; transisi dari starting juga mungkin bila kegagalan terus berlanjut setelah masa hangar habis.
  4. Pulih dua arah — probe yang kembali lulus mengangkat status dari unhealthy ke healthy; pemeriksaan tidak pernah berhenti di tengah jalan.

Siklus itu mudah disaksikan langsung. Budi menambahkan blok healthcheck di atas lalu mengulang siklus start:

bash
$ docker compose up -d
$ docker compose ps

Output yang Dihasilkan:

text
NAME           IMAGE               COMMAND                  SERVICE   CREATED         STATUS                            PORTS
katalog-db-1   postgres:18-alpine  "docker-entrypoint.s…"   db        22 seconds ago  Up 21 seconds (healthy)           5432/tcp
katalog-web-1  katalog-web         "docker-entrypoint.s…"   web       22 seconds ago  Up 12 seconds (health: starting)   0.0.0.0:8080->3000/tcp

Dua puluh detik kemudian, gambar yang sama berubah kata:

bash
$ docker compose ps

Output yang Dihasilkan:

text
NAME           IMAGE               COMMAND                  SERVICE   CREATED         STATUS                    PORTS
katalog-db-1   postgres:18-alpine  "docker-entrypoint.s…"   db        41 seconds ago  Up 40 seconds (healthy)   5432/tcp
katalog-web-1  katalog-web         "docker-entrypoint.s…"   web       41 seconds ago  Up 31 seconds (healthy)   0.0.0.0:8080->3000/tcp

Perjalanan web dari (health: starting) ke (healthy) memakan waktu sesuai desain: start_period 15 detik memberi ruang bagi pool koneksi, dan start_interval 5 detik memastikan status tercapai segera setelah endpoint menjawab. Kedua service kini membawa lencana kesehatan yang sama.

Membaca Laporan Kesehatan lewat docker inspect #

Status satu kata di ps adalah ringkasan dari laporan lengkap yang tersimpan pada container. Recap singkat dari Chapter HEALTHCHECK: field .State.Health memuat status, penghitung kegagalan, dan riwayat probe:

bash
$ docker inspect --format '{{json .State.Health}}' katalog-web-1

Output yang Dihasilkan:

json
{
    "Status": "healthy",
    "FailingStreak": 0,
    "Log": [
        {
            "Start": "2026-10-14T09:12:33Z",
            "End": "2026-10-14T09:12:33Z",
            "ExitCode": 0,
            "Output": ""
        }
    ]
}

Mari kita telaah tiga field di atas:

  1. Status: healthy — kesimpulan akhir daemon atas seluruh riwayat probe; kata yang sama yang tampil dalam tanda kurung pada docker compose ps.
  2. FailingStreak: 0 — penghitung kegagalan beruntun saat ini; angka yang merambat naik menuju retries adalah alarm dini sebelum status benar-benar turun.
  3. Log — riwayat probe terakhir lengkap dengan waktu, exit code, dan keluaran stdout; Output kosong karena wget -q memang diam, dan saat probe gagal kolom inilah yang menampung pesan untuk dibaca.

Compose Tidak Memulai Ulang Service yang Unhealthy #

Status unhealthy mempengaruhi gerbang depends_on di masa up, tetapi ada satu batas perilaku yang mengejutkan hampir semua orang.

compose.yaml katalog Final #

Seluruh bagian puzzle yang kita kumpulkan sejak volume kini tergabung dalam satu berkas. Inilah compose.yaml katalog dalam bentuk terlengkapnya sejauh ini:

yaml
services:
  web:
    build: .
    ports:
      - "8080:3000"
    env_file:
      - .env
    environment:
      DATABASE_URL: postgres://katalog:${POSTGRES_PASSWORD}@db:5432/katalog
    restart: on-failure
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/healthz"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 15s
      start_interval: 5s

  db:
    image: postgres:18-alpine
    environment:
      POSTGRES_USER: katalog
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: katalog
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
      start_interval: 2s

volumes:
  db-data:

Mari kita telaah kerja sama antarbagiannya:

  1. volumes dan blok teratas db-data — data bertahan melewati down, janji Chapter Volume dan Bind Mount versi Compose.
  2. env_file dan interpolasi ${POSTGRES_PASSWORD} — kredensial hidup satu sumber di .env, disuntikkan ke container dan ke teks konfigurasi sekaligus.
  3. depends_on dengan condition: service_healthy — web menyala hanya setelah probe db lulus; urutan start tidak lagi berjudi.
  4. Dua healthcheck yang saling menjaga — db diuji dengan pg_isready lewat CMD-SHELL dan variabel $$, web diuji dengan wget --spider ke /healthz lewat CMD; keduanya memakai start_period sesuai kebutuhan masing-masing.
  5. restart: on-failure — lapisan ketahanan untuk kegagalan yang lolos dari gerbang, menutup celah terakhir yang kita ketahui.

Stack katalog kini sehat secara terukur: data persisten, konfigurasi terpusat, urutan start terkendali, dan dua probe yang menjunjung kejujuran status. Namun dua kelas kebutuhan masih memakai jalan pintas yang kurang rapi: berkas konfigurasi utuh seperti nginx.conf masih terpaksa di-bind manual, dan password masih berpindah sebagai string polos di environment yang terbaca siapa pun yang bisa menjalankan docker inspect. Compose menyediakan dua mekanisme khusus untuk keduanya — configs untuk berkas konfigurasi, secrets untuk data sensitif — dan keduanya adalah materi Chapter Config dan Secret.

Referensi #