Chapter Container Health menutup dengan pengamatan yang tepat: container yang sakit namun tetap hidup adalah nasib yang cukup ramah — probe masih mengetuk, status masih bisa berubah menjadi unhealthy, dan kita masih sempat bertindak sebelum semuanya terlampau. Kelas kecelakaan berikutnya jauh lebih dingin: container yang berhenti total tidak lagi bisa di-probe, tidak lagi menjawab, dan tidak lagi muncul di docker ps. Kabar baiknya, kematian container hampir tidak pernah terjadi diam-diam: daemon mencatat exit code proses utamanya, waktu berhentinya, penanda khusus bila proses dibunuh kelaparan memori, sampai detik ketika event die dituliskan. Stack toko kita baru saja mendapat korban semacam ini: laporan-harian — service job yang dijadwalkan scheduler menyala setiap tengah malam untuk merangkum penjualan sehari dan mengirim ringkasan ke API tim marketing — tidak mengirim apa pun pada dini hari 14 Oktober. Chapter ini melakukan otopsi lengkap atas kematian itu dengan merangkai empat alat yang sudah kita kumpulkan sepanjang section: docker ps -a, docker inspect, docker logs, dan docker events.

Exit Code sebagai Berita Kematian Container #

Setiap proses Unix berakhir dengan satu angka — exit code — dan container mewarisi angka milik proses utamanya (PID 1). Konvensinya sudah kita bedah tuntas di chapter Memahami Exit Code: 0 berarti sukses, kode 1 sampai 125 adalah wilayah aplikasi, dan kode 128 ke atas berarti proses dihentikan sinyal — 137 untuk SIGKILL (128+9), 143 untuk SIGTERM (128+15) — sementara 125, 126, dan 127 milik sisi Docker. Pada chapter tersebut kita memproduksi kegagalan kecil secara sengaja; sekarang kegagalan datang sendiri tanpa undangan, dan tugas kita membacanya seperti otopsi.

Papan docker ps -a adalah pintu masuknya. Format tabel ringkas membuat kita bisa memindai seluruh container yang sudah berhenti beserta kode keluarnya:

bash
$ docker ps -a --filter status=exited --format 'table {{.Names}}\t{{.Status}}'

Output yang Dihasilkan:

text
NAMES               STATUS
laporan-harian      Exited (1) 9 hours ago
toko-migrate-1      Exited (0) 12 hours ago
uji-nyasar          Exited (127) 3 days ago

Mari kita telaah perintah dan tabel tersebut:

  1. --filter status=exited menyaring hanya container berstatus Exited — container yang hidup tidak ikut mengganggu pemeriksaan pagi ini.
  2. --format 'table ...' memilih kolom yang relevan untuk diagnosis (nama dan status keluar) sehingga daftar panjang tetap mudah dipindai mata.
  3. toko-migrate-1 dengan Exited (0) bukanlah kegagalan — job migrasi memang dirancang selesai, dan kode 0 setelah gerbang service_completed_successfully di chapter Startup dan Dependency justru berarti tugasnya tuntas.
  4. laporan-harian dengan Exited (1) adalah korban kita — kode 1 adalah bahasa klasik aplikasi yang menemui galat lalu menyerah; sisanya hanya menunggu dibedah.

Otopsi dengan docker inspect #

Kolom STATUS memberi angkanya; struktur State pada docker inspect memberi konteksnya. Lima field berikut adalah set standar otopsi kita:

bash
$ docker inspect laporan-harian --format 'Status={{.State.Status}} ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}} FinishedAt={{.State.FinishedAt}}'

Output yang Dihasilkan:

text
Status=exited ExitCode=1 OOMKilled=false Error= FinishedAt=2026-10-13T16:15:03.902183744Z

Mari kita bedah kelima field tersebut satu per satu:

  1. State.Status adalah kondisi terakhir container — exited, running, atau restarting — versi resmi daemon, bukan tebakan kolom ps.
  2. State.ExitCode adalah angka warisan PID 1 — inti berita kematian yang sama dengan yang terlihat di kolom STATUS.
  3. State.OOMKilled bernilai true hanya bila kernel membunuh proses karena cgroup memori container kehabisan ruang — petunjuk emas ketika exit code adalah 137.
  4. State.Error memuat pesan galat dari sisi daemon, bukan dari aplikasi.
  5. State.FinishedAt adalah waktu berhentinya dalam UTC — angka yang bisa disandingkan dengan jejak waktu di log aplikasi dan di docker events.

Peta Kecurigaan per Kode Keluar #

Angka saja belum cukup menuntun tindakan. Tabel berikut memetakan tujuh kode yang paling sering muncul ke langkah diagnosis pertama yang paling produktif:

Exit codeArti paling mungkinLangkah diagnosis pertama
0Tugas selesai normal — khususnya container batch/jobBukan masalah; baca docker logs bila ingin hasil kerjanya
1Aplikasi menemui galat lalu keluarBaca docker logs, cari baris terakhir sebelum mati
125Daemon gagal menjalankan container — flag atau nilainya tidak validPeriksa penulisan perintah docker run; pesan galatnya tampil di terminal
126Perintah ditemukan tetapi tidak bisa dieksekusiPeriksa bit eksekusi berkas (chmod +x) dan baris shebang
127Perintah tidak ditemukan di dalam imagePeriksa ejaan nama biner dan variabel PATH; verifikasi image yang dipakai
137Dihentikan SIGKILL — docker kill, tenggang stop habis, atau OOMBaca State.OOMKilled; bila ragu, periksa dmesg
143Dihentikan SIGTERM — umumnya docker stopWajar saat dihentikan; curiga bila muncul tanpa stop dari siapa pun

Kode 137 pantas mendapat perhatian ekstra karena memiliki dua tersangka besar. Tersangka pertama adalah OOM killer kernel — dan bila State.OOMKilled bernilai true, kasusnya langsung jelas: arah perbaikannya adalah limit memori atau kebocoran di aplikasi. Tersangka kedua adalah SIGKILL dari tangan manusia — docker kill, atau masa tenggang docker stop yang kehabisan waktu. Bila OOMKilled bernilai false namun kecurigaan OOM belum gugur, catatan kernel di host menjadi hakim terakhir:

bash
$ sudo dmesg | grep -i 'out of memory'

Output yang Dihasilkan (dipotong):

text
[4831.204812] Memory cgroup out of memory: Killed process 1873 (python3) total-vm:418816kB, anon-rss:251392kB

Baris tersebut adalah pengakuan tertulis kernel: proses bernama python3 di dalam cgroup suatu container dibunuh karena memakai memori melebihi plafonnya — mekanisme cgroups yang kita bahas di chapter Resource Limit. Tidak ada baris seperti ini di dmesg, tidak ada OOM; tersangka berpindah kembali ke tangan manusia.

Merangkai Empat Alat dalam Satu Otopsi #

Satu perintah jarang cukup untuk menutup kasus. Alur lengkapnya dirangkai dari empat alat: ps -a menunjukkan siapa yang mati, inspect menjelaskan bagaimana ia mati, logs mengisi kata-kata terakhirnya, dan events mengunci kronologinya. Dua langkah pertama sudah kita jalankan: laporan-harian berhenti dengan kode 1, bukan OOM, pada pukul 16:15:03Z UTC. Kini giliran kata-kata terakhirnya:

bash
$ docker logs --tail 6 laporan-harian

Output yang Dihasilkan:

text
[2026-10-13 23:15:00] laporan harian dimulai
[2026-10-13 23:15:00] membaca konfigurasi lingkungan...
Traceback (most recent call last):
  File "/app/laporan.py", line 41, in <module>
    token = os.environ['API_TOKEN']
             ~~~~~~~~~^^^^^^^^^^^^^
KeyError: 'API_TOKEN'

Mari kita telaah jejak yang ditinggalkan log tersebut:

  1. Dua baris pertama sehat — aplikasi menyala dan langsung membaca konfigurasi lingkungan; kegagalan baru terjadi di langkah berikutnya.
  2. KeyError: 'API_TOKEN' adalah vonisnya — skrip Python membaca os.environ['API_TOKEN'] dan variabel tersebut tidak ada di environment container; traceback lengkap dengan nomor barisnya adalah pola yang sudah kita kenal dari chapter Docker Logs.
  3. Exit code 1 turun secara otomatis — Python menutup dirinya dengan kode 1 untuk exception yang tidak tertangani; tidak ada yang salah dengan container atau daemon, yang kurang cuma satu baris environment.
  4. Waktunya 23:15 waktu lokal — aplikasi mencetak timestamp sendiri dengan zona waktu mesin (WIB), dan angka ini akan menjadi kunci saat disandingkan dengan arsip UTC pada inspect di bawah.

Terakhir, kita kunci kronologi dari sisi daemon. Karena inspect sudah memberi tahu waktu kematiannya, jendela waktu events bisa ditulis presisi mengelilingi angka itu:

bash
$ docker events --since '2026-10-13T16:14:55Z' --until '2026-10-13T16:15:10Z' --filter container=laporan-harian

Output yang Dihasilkan:

text
2026-10-13T23:15:00.104923155+07:00 network connect 6b1e2f3a4b5c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e (container=6b1e2f3a4b5c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e, name=bridge, type=bridge)
2026-10-13T23:15:00.208511907+07:00 container start 6b1e2f3a4b5c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e (image=laporan:1.3, name=laporan-harian)
2026-10-13T23:15:03.902183744+07:00 container die 6b1e2f3a4b5c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e (image=laporan:1.3, name=laporan-harian, exitCode=1)
2026-10-13T23:15:03.981350441+07:00 network disconnect 6b1e2f3a4b5c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e (container=6b1e2f3a4b5c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e, name=bridge, type=bridge)

Mari kita bedah bagaimana keempat baris itu saling mengunci dengan bukti-bukti sebelumnya:

  1. container die dan FinishedAt adalah satu peristiwa yang sama — event menunjuk 23:15:03.902183744, sedangkan inspect menyimpan 2026-10-13T16:15:03.902183744Z; nanodetik 902183744 identik di keduanya karena memang dicatat dari sumber yang sama.
  2. Atribut exitCode=1 pada event die cocok dengan State.ExitCode — tiga alat yang berbeda menyebut satu angka, dan angka itu konsisten.
  3. Tiga jam, dua zona waktu, satu momen — FinishedAt tersimpan dalam UTC (16:15:03Z), docker events merendernya dalam zona waktu mesin kita — WIB, tujuh jam di depan UTC, konvensi yang kita kenal dari chapter Docker Events — dan aplikasi mencetak 23:15 dari jam lokalnya sendiri. 16:15:03 ditambah tujuh jam memang menjadi 23:15:03; ketiga angka itu tidak bertentangan, semuanya menunjuk detik yang sama.
  4. Umur container hanya 3,7 detik — jarak antara start dan die — cukup bagi Python untuk menyala, gagal membaca environment, dan tumbang.

Empat kesaksian yang saling mengunci membuat perbaikannya tinggal satu langkah: menambahkan API_TOKEN pada environment service laporan-harian — persis pola fail-fast yang sehat, karena aplikasi menolak bekerja tanpa bekalnya. Sebagai penutup, docker wait — yang memblokir terminal sampai container berhenti lalu mencetak exit code-nya, sebagaimana kita praktikkan di chapter Memahami Exit Code — tetap menjadi alat andalan untuk skrip yang menunggu job secara programatik; namun untuk otopsi manual, rantai ps -a → inspect → logs → events inilah yang memberi konteks paling kaya.

Exit code memberi tahu kapan dan bagaimana sebuah container mati. Namun ada kelas kegagalan yang justru tidak membunuh container sama sekali: aplikasi hidup dan sehat di mata docker ps, tetapi tidak bisa dijangkau siapa pun. Kelas kegagalan semacam itu bersembunyi di lapisan jaringan, dan membongkarnya menuntut resep yang bergerak dari dalam container ke luar — materi chapter Debugging Network.

Referensi #