Baris health_status: healthy yang menutup Chapter Docker Events lahir dari sumber yang unik: bukan proses yang hidup atau mati, melainkan probe — perintah kecil yang daemon jalankan berkala di dalam container untuk menanyakan langsung ke aplikasi. Kita telah dua kali menjumpai konsep ini: probe ditanam ke image lewat HEALTHCHECK pada Chapter HEALTHCHECK, lalu dideklarasikan per service di compose.yaml pada Chapter Healthcheck dan Service Readiness — dan keduanya bekerja diam-diam di belakang gerbang service_healthy yang menjaga urutan start stack toko. Chapter ini membalik posisi kita dari perancang probe menjadi pembaca statusnya: apa yang dikatakan healthy, unhealthy, dan starting, bagaimana membaca laporan lengkapnya, dan — pertanyaan yang paling sering mengejutkan operator baru — apa yang Docker lakukan ketika container-nya sakit berat.

Probe yang Menilai Kesehatan #

Recap singkat untuk menyamankan pijakan: HEALTHCHECK pada Dockerfile menanam probe ke dalam image, sedangkan kunci healthcheck di compose.yaml menimbangnya kembali per service tanpa menyentuh Dockerfile pihak ketiga. Backend toko memakai bentuk kedua — probe yang mengetuk endpoint /healthz, jalur yang di dalamnya mengeksekusi SELECT 1 sungguhan ke database:

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

Probe itu menjalankan dua tugas sekaligus di stack toko: menjaga kejujuran kolom STATUS — operator melihat kesehatan, bukan sekadar napas proses — dan membuka gerbang depends_on dengan kondisi service_healthy, sehingga proxy hanya menyala setelah backend benar-benar menjawab; keduanya sudah kita rancang tuntas pada Chapter Startup dan Dependency. Nilai start_period adalah masa grasi: selama lima detik pertama kegagalan probe tidak dihitung, memberi Node waktu membaca environment dan membuka pool koneksi sebelum dinilai.

Siklus statusnya ringkas dan bisa diprediksi. Container yang baru menyala berstatus health: starting; begitu probe pertama lulus, status naik menjadi healthy. Setiap kegagalan probe menambah penghitung kegagalan beruntun — FailingStreak — dan begitu penghitung menyamai retries (tiga untuk backend), status jatuh menjadi unhealthy. Probe tidak pernah berhenti setelah kegagalan: bila kelak probe lulus kembali, penghitung direset dan status pulih ke healthy dengan sendirinya.

Backend Demam Saat Proses Tetap Hidup #

Skenario chapter ini datang dari pagi yang sibuk. Siti menarik laporan rekapnya dan perubahan kecil pada konfigurasi service db, lalu menjalankan docker compose up -d seperti biasa. Sejam kemudian Budi membuka daftar stack dan menemukan pemandangan yang membingungkan pendatang baru:

bash
$ docker compose ps

Output yang Dihasilkan:

text
NAME             IMAGE              COMMAND                  SERVICE   CREATED             STATUS                    PORTS
toko-proxy-1     nginx:1.29-alpine  "/docker-entrypoint.…"  proxy     2 hours ago         Up 2 hours                0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
toko-frontend-1  nginx:1.29-alpine  "/docker-entrypoint.…"  frontend  2 hours ago         Up 2 hours                80/tcp
toko-backend-1   toko-api:dev       "docker-entrypoint.s…"  backend   About an hour ago   Up 70 minutes (unhealthy)  3000/tcp
toko-cache-1     redis:7-alpine     "docker-entrypoint.s…"  cache     2 hours ago         Up 2 hours (healthy)
toko-db-1        postgres:18-alpine "docker-entrypoint.s…"  db        About a minute ago  Up 36 seconds (healthy)    5432/tcp

Mari kita telaah kejanggalan pada tabel itu:

  1. Backend demam, tetapi prosesnya tetap hidup — Up 70 minutes (unhealthy) adalah paradoks yang justru inti healthcheck: proses node tidak pernah mati, docker ps versi lama akan menyebutnya sehat, tetapi probe menilai sebaliknya. Kolom STATUS akhirnya jujur.
  2. Angka (unhealthy) bukan hiasan — status itu dijaga oleh probe /healthz; frontend yang tidak punya healthcheck hanya menampilkan Up 2 hours polos, dan bandingannya membuat makna tanda kurung terlihat jelas.
  3. Baris db menyimpan petunjuk penyebab — toko-db-1 jauh lebih muda dari seluruh tetangganya: diciptakan About a minute ago, berjalan baru 36 seconds, padahal stack berumur dua jam lebih. Container db yang baru diciptakan sesaat lalu adalah kabar besar bagi siapa pun yang menggantungkan koneksi padanya.

Kronologi Sehat ke Sakit dari Aliran Events #

Kosakata Chapter Docker Events terpakai langsung untuk membangun garis waktu backend — penyaring per objek, mode historis, dan jendela dua jam ke belakang:

bash
$ docker events --since 2h --until 0s --filter container=toko-backend-1

Output yang Dihasilkan (baris attach dan kawan-kawannya dipotong):

text
2026-10-13T08:31:33.613547882+07:00 container create 20269502f039d2d914b5845a97033f9a7ab8bbaf7ab58440affe76c390453a9c (image=toko-api:dev, name=toko-backend-1)
2026-10-13T08:31:34.104891245+07:00 container start 20269502f039d2d914b58440affe76c390453a9c (image=toko-api:dev, name=toko-backend-1)
2026-10-13T08:31:40.712401337+07:00 container health_status: healthy 20269502f039d2d914b58440affe76c390453a9c (image=toko-api:dev, name=toko-backend-1)
2026-10-13T09:41:30.402119845+07:00 container health_status: unhealthy 20269502f039d2d914b5845a97033f9a7ab8bbaf7ab58440affe76c390453a9c (image=toko-api:dev, name=toko-backend-1)

Mari kita telaah garis waktu tersebut:

  1. Container backend lahir pukul 08:31:33 — hasil docker compose up -d --build yang Siti jalankan setelah menarik perubahan pagi ini; enam detik kemudian health_status: healthy tercatat — masa grasi start_period lima detik berlalu, probe start_interval mengetuk, /healthz menjawab 200.
  2. Status jatuh pukul 09:41:30 — tujuh puluh menit berstatus sehat, lalu health_status: unhealthy; tiga kegagalan beruntun pada interval sepuluh detik berujung pada momen ini.
  3. Tidak ada satu pun kejadian die, kill, atau restart untuk backend — kronik daemon membuktikan secara eksplisit bahwa proses di dalamnya tidak pernah mati; yang berubah hanyalah putusan probe. health_status memang kejadian satu-satunya yang bisa lahir saat container “hidup tapi sakit”.

Laporan Lengkap di State Health #

Kata dalam tanda kurung adalah ringkasan; berita lengkapnya hidup di field .State.Health — pola pipa yang kita kenal dari Chapter Docker Inspect:

bash
$ docker inspect --format '{{json .State.Health}}' toko-backend-1 | python3 -m json.tool

Output yang Dihasilkan:

json
{
    "Status": "unhealthy",
    "FailingStreak": 4,
    "Log": [
        {
            "Start": "2026-10-13T02:41:00.104512312Z",
            "End": "2026-10-13T02:41:00.118044293Z",
            "ExitCode": 0,
            "Output": ""
        },
        {
            "Start": "2026-10-13T02:41:10.209871453Z",
            "End": "2026-10-13T02:41:10.224193852Z",
            "ExitCode": 1,
            "Output": "wget: server returned error: HTTP/1.1 500 Internal Server Error"
        },
        {
            "Start": "2026-10-13T02:41:20.310294716Z",
            "End": "2026-10-13T02:41:20.318044293Z",
            "ExitCode": 1,
            "Output": "wget: server returned error: HTTP/1.1 500 Internal Server Error"
        },
        {
            "Start": "2026-10-13T02:41:30.402119845Z",
            "End": "2026-10-13T02:41:30.415712221Z",
            "ExitCode": 1,
            "Output": "wget: server returned error: HTTP/1.1 500 Internal Server Error"
        },
        {
            "Start": "2026-10-13T02:41:40.513547882Z",
            "End": "2026-10-13T02:41:40.524193852Z",
            "ExitCode": 1,
            "Output": "wget: server returned error: HTTP/1.1 500 Internal Server Error"
        }
    ]
}

Mari kita telaah isi laporan tersebut:

  1. Status: unhealthy — putusan akhir yang diringkas docker compose ps menjadi kata dalam tanda kurung.
  2. FailingStreak: 4 — empat kegagalan beruntun saat laporan dibaca; status sudah jatuh sejak kegagalan ketiga, dan probe tetap berjalan tiap sepuluh detik tanpa peduli statusnya.
  3. Log menyimpan lima pemeriksaan terakhir — riwayat probe bergeser seperti jendela: entri paling tua terdorong keluar saat entri baru lahir.
  4. Entri pertama adalah saksi terakhir yang sehat — ExitCode: 0 dengan Output kosong pada pukul 02:41:00Z: probe wget --spider -q memang hening ketika sukses.
  5. Empat entri berikutnya menceritakan penyakitnya — ExitCode: 1 dan wget: server returned error: HTTP/1.1 500 Internal Server Error: /healthz tetap menjawab, tetapi dengan status 500 — handler di dalamnya menangkap galat dari pool.query("SELECT 1") dan membalas db tidak siap.
  6. Stempel waktunya berzona Z, bukan +07:00 — 02:41:30Z dan 09:41:30 WIB adalah momen yang sama ditulis dua cara, tujuh jam berselisih notasi; inilah pasangan yang harus selalu dikonversi saat kita menyandingkan Health.Log dengan docker events.

Penyebab 500 itu kini tinggal dirangkai dari dua barang bukti yang sudah kita pegang. Baris db pada docker compose ps memperlihatkan container yang baru berumur 36 detik; kronik lengkapnya terbaca lewat penyaring yang sama:

bash
$ docker events --since 10m --until 0s --filter container=toko-db-1

Output yang Dihasilkan (dipotong):

text
2026-10-13T09:41:05.104512312+07:00 container stop 4ccc495ebdf6562ba1a5b95f4e15fcf3cd4812941a30e3f588778b2134973f98 (image=postgres:18-alpine, name=toko-db-1)
2026-10-13T09:41:05.209871453+07:00 container die 4ccc495ebdf6562ba1a5b95f4e15fcf3cd4812941a30e3f588778b2134973f98 (image=postgres:18-alpine, name=toko-db-1, exitCode=0)
2026-10-13T09:41:05.401229801+07:00 container destroy 4ccc495ebdf6562ba1a5b95f4e15fcf3cd4812941a30e3f588778b2134973f98 (image=postgres:18-alpine, name=toko-db-1)
2026-10-13T09:41:07.812401337+07:00 container create deb60c8a4e4cdeb12dce33330749999d01be15eee53a4dd1e7246fbfef31ea94 (image=postgres:18-alpine, name=toko-db-1)
2026-10-13T09:41:08.104891245+07:00 container start deb60c8a4e4cdeb12dce33330749999d01be15eee53a4dd1e7246fbfef31ea94 (image=postgres:18-alpine, name=toko-db-1)
...
2026-10-13T09:41:19.613547882+07:00 container health_status: healthy deb60c8a4e4cdeb12dce33330749999d01be15eee53a4dd1e7246fbfef31ea94 (image=postgres:18-alpine, name=toko-db-1)

Kronik itu melengkapi cerita: perubahan kecil pada service db memicu Compose menghentikan, menghancurkan, lalu menciptakan ulang container db — ID lama 4ccc495e... lenyap, ID baru deb60c8a... lahir, dan probe pg_isready di sisi db langsung lulus kembali sehingga db tampak (healthy). Yang tidak ikut diperbarui adalah pool koneksi di dalam backend: koneksi-koneksi yang dibuka pagi tadi masih menggenggam alamat container db lama yang sudah tiada, dan tanpa logika coba-ulang, setiap SELECT 1 dari /healthz dijawab kegagalan. Kejadian ini persis skenario yang pernah kami peringatkan pada Chapter Startup dan Dependency: gerbang depends_on hanya menjaga urutan start — sesudahnya, container yang dibuat ulang tidak diumumkan kepada tetangganya.

Docker Tidak Menyembuhkan yang Sakit #

Di titik inilah hampir semua operator baru bersalah paham. Dari seluruh peralatan yang kita kenal — restart policy, depends_on, healthcheck — wajar bila kita mengira daemon akan menyentuh backend yang sakit. Diagram berikut memetakan apa yang sebenarnya terjadi pada dua kelas kejadian yang berbeda:

Diagram di atas memperlihatkan dua jalur yang tidak pernah bertemu: jalur kiri adalah kejadian kesehatan — probe gagal, status turun, dan daemon berhenti di situ; jalur kanan adalah kejadian hidup-matikan — proses keluar, exit code tercatat, dan restart policy yang menimbangnya.

Reaksi atas container unhealthy, jadi, harus datang dari kita. Tiga pilihan tersusun dari yang paling manual sampai yang paling otomatis:

  1. Tangan manusia — docker compose restart backend memutus simpulnya seketika: proses dinyalakan ulang, pool koneksi lahir dari nol, dan /healthz kembali menjawab 200. Jalur ini pas untuk stack development seperti toko.
  2. Watchdog kecil — skrip sederhana yang memungut pekerjaan ini secara berkala:
bash
while true; do
  for nama in $(docker ps --filter health=unhealthy --format '{{.Names}}'); do
    echo "watchdog: memulai ulang $nama"
    docker restart "$nama"
  done
  sleep 30
done

Skrip tersebut memakai filter health=unhealthy milik docker ps — penyaring yang lahir dari status yang kita bahas hari ini — lalu memulai ulang setiap korban tiap tiga puluh detik. Komunitas mengemas pola yang sama dalam image siap pakai bernama autoheal; prinsipnya identik, tinggal dipasang sebagai container. Kedua bentuknya sama-sama kasar: restart adalah simptomatik, bukan penyembuhan — bila akar masalahnya koneksi yang tidak pernah pulih, watchdog akan memutar-ulang container selamanya.

  1. Orchestrator — Swarm dan Kubernetes memperlakukan kegagalan probe sebagai urusan penjadwalan: container yang berkali-kali gagal diganti replikanya di mesin lain secara otomatis. Membawa kesehatan ke dalam keputusan penjadwalan adalah lompatan level yang kita jajaki pada Section Orchestration dan Ekosistem.

Untuk toko hari ini, tangan manusia memang cukup. Budi menjalankan jalur pertama:

bash
$ docker compose restart backend

Output yang Dihasilkan:

text
[+] Restarting 1/1
 ✔ Container toko-backend-1  Started  1.8s

Menit berikutnya kolom STATUS-nya menempuh siklus yang sama seperti pagi tadi — health: starting selama masa grasi, lalu (healthy) begitu probe pertama lulus; pool koneksi yang baru terbuka ke container db yang baru, dan docker events mencatat satu baris health_status: healthy sebagai penutup insiden. Pola lengkapnya — memadukan healthcheck, restart policy, dan probe yang dirancang untuk produksi — adalah materi Chapter Healthcheck dan Restart Policy di Section Deployment dan Production.

Kamus Status dan Artinya #

Seluruh kosakata kolom STATUS untuk container ber-healthcheck kini terkumpul dalam satu tabel:

Status di kolom STATUSArtinyaSiapa yang menuliskannya
health: startingmasa grasi start_period berjalan; kegagalan probe tidak dihitungdaemon, sejak container menyala
healthyprobe terakhir lulus; FailingStreak direset noldaemon, setelah probe keluar exit 0
unhealthykegagalan beruntun mencapai retries; probe tetap berjalandaemon, setelah retries kegagalan
tanpa tanda kurungcontainer tanpa healthcheck — hanya napas proses yang dilaporkantidak ada probe yang terpasang

Status kesehatan menjawab satu pertanyaan dengan sangat baik: apakah aplikasi masih sanggup melayani. Tetapi container yang sakit hari ini tetap hidup — nasib yang jauh lebih ramah dibanding kelas kecelakaan yang menanti di chapter berikutnya: container yang mati sama sekali. Exited (137) tanpa pesan perpisahan, Restarting (1) yang berputar tanpa henti, dan rantai ps → inspect → logs → events yang merangkai diagnosisnya — itulah pembahasan Chapter Membaca Container Exit.

Referensi #