Setiap proses yang kita jalankan sepanjang section ini — docker run maupun docker exec — pada akhirnya berakhir, dan setiap akhir meninggalkan satu angka kecil antara 0 sampai 255 sebagai warisan terakhirnya. Kita sudah dua kali berpapasan dengan angka itu: quirky_einstein tutup usia dengan Exited (0), sedangkan web sempat tercatat Exited (137) ketika kita mengujinya dengan docker kill. Angka-angka semacam itu bukan hiasan kolom STATUS — angka tersebut adalah bahasa diagnosis paling murah yang dimiliki container: tanpa membuka log, tanpa memasuki container, kita bisa mempersempit dugaan penyebab kematian sebuah proses. Chapter penutup section ini membedah konvensi di balik exit code, mempraktikkan cara menangkapnya dari shell, menungguinya secara langsung, dan membacanya dari status container — bekal yang membuat kita tidak pernah lagi menganggap Exited (137) sebagai teka-teki.
Exit Code dari Sudut Pandang Shell #
Exit code lahir dari konvensi Unix yang setua sistem operasi itu sendiri: saat sebuah proses berakhir, proses tersebut menyerahkan satu angka status kepada induknya. Nilai 0 berarti sukses; nilai selain 0 berarti gagal dengan sebab yang tersirat di angkanya. Shell menyimpan angka status perintah terakhir pada variabel $?, dan docker run yang berjalan dalam mode terpasang meneruskan exit code milik PID 1 container langsung ke sana. Karena image alpine:3.20 sudah tersimpan di mesin kita sejak Chapter Menghapus Container, percobaan berikut berlangsung seketika:
$ docker run --rm alpine:3.20 sh -c 'exit 7'
$ echo $?Output yang Dihasilkan:
7Mari kita telaah apa yang barusan terjadi:
sh -c 'exit 7'sebagai PID 1 — shell di dalam container menjalankan satu instruksiexit 7lalu berakhir dengan kode 7; proses utama container berumur hanya beberapa milidetik dan tidak mencetak apa pun.docker runmeneruskan kode tersebut — CLI menunggu container selesai, mengambil kode keluar PID 1, lalu menutup dirinya dengan kode yang sama;$?milik shell kita menangkap angka7itu utuh.--rmmembuat percobaan steril — container bekas langsung dibersihkan sehinggadocker ps -atidak menampilkan jejak apa pun; satu-satunya bukti bahwa percobaan pernah terjadi adalah angka di$?.- Kode 3 sampai 255 adalah wilayah aplikasi — angka
7barusan kami pilih sendiri; aplikasi bebas mendefinisikan arti tiap kode, dan kebiasaan membaca$?berurutan dengandocker runadalah cara tercepat menguji kode-kode tersebut. - Mode detached tidak meneruskan kode —
docker run -dmencetak ID container dan segera mengembalikan terminal; exit code container tersebut tidak bisa ditangkap lewat$?dan harus dibaca belakangan lewat status container.
Status container memang menjadi tempat kediaman resmi angka tersebut: kolom STATUS pada docker ps -a menampilkan Exited (N) untuk setiap container yang sudah berhenti — Exited (0) yang kita lihat pada quirky_einstein, Exited (137) yang pernah tercatat pada web, dan sebentar lagi Exited (143) pada percobaan kita.
Tiga Konvensi Exit Code #
Tiga keluarga konvensi mengisi rentang 0 sampai 255, dan ketiganya perlu dibedakan karena berasal dari pelaku yang berbeda: aplikasi, Docker, dan kernel.
Konvensi Unix #
Keluarga pertama adalah kesepakatan universal dunia Unix. Kode 0 menyatakan sukses penuh — program menuntaskan tugasnya tanpa keluhan. Kode 1 adalah kode kegagalan aplikasi yang paling umum: program mencoba, gagal, lalu melaporkan dirinya — error pembacaan berkas, koneksi ditolak, atau exception yang tidak tertangani. Kode 2 secara tradisi menandai kesalahan pemakaian: argumen perintah salah tulis atau flag tidak dikenal; banyak program CLI menjatuhkan kode ini sambil mencetak ringkasan cara pakainya. Percobaan exit 7 di atas menunjukkan bahwa sisanya — 3 hingga 255 — adalah wilayah bebas yang artinya ditentukan masing-masing aplikasi.
Konvensi Docker #
Keluarga kedua berasal dari Docker CLI sendiri, untuk keadaan ketika container gagal menjalankan sesuatu. Kode 125 menandakan daemon menolak atau gagal menjalankan perintah docker run sebelum container sempat menyala — penyebabnya berupa flag yang salah atau nilainya tidak valid, misalnya konflik nama container yang sudah dipakai. Kode 126 berarti perintah yang dimaksud ada di dalam image tetapi tidak bisa dieksekusi — khasnya berkas tanpa bit eksekusi. Kode 127 adalah yang paling sering menjumpai pemula: perintah yang diminta tidak ditemukan sama sekali di dalam image. Kita reproduksi kejadiannya secara sengaja:
$ docker run --rm alpine:3.20 tidakadaOutput yang Dihasilkan:
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "tidakada": executable file not found in $PATH: unknown.$ echo $?Output yang Dihasilkan:
127Mari kita telaah pasangan output di atas:
- Pesan galat yang panjang adalah jejak perjalanannya — permintaan kita berjalan dari CLI ke daemon, ke containerd, sampai ke runc yang hendak melahirkan proses; rantai ini persis arsitektur berlapis yang kita bahas pada Chapter Arsitektur Docker.
- Intinya ada di ujung pesan —
exec: "tidakada": executable file not found in $PATHberarti runc tidak menemukan biner bernamatidakadadi direktori pencarian image alpine; nama perintah kita memang karangan. - CLI menutup dirinya dengan kode 127 — sesuai konvensi shell yang sudah ada sebelum Docker lahir: perintah tidak ditemukan berarti 127; container gagal menjalankan apapun sehingga tidak ada exit code aplikasi yang bisa dilaporkan.
- Kejadiannya sering tidak disengaja — versi paling umum dari galat ini adalah image yang keliru disebut atau aplikasi dalam image yang berpindah jalur biner; kode 127 pada
docker ps -aadalah petunjuk kuat untuk memeriksa nama perintah dan image yang dipakai.
Konvensi Sinyal 128+N #
Keluarga ketiga diisi oleh kernel. Bila sebuah proses berakhir karena dibunuh sinyal nomor N, kernel melaporkan status keluarnya sebagai 128 + N — konvensi yang diwarisi Docker utuh. Dua angka paling penting: 137 berarti 128 + 9, yaitu SIGKILL, dan 143 berarti 128 + 15, yaitu SIGTERM. Makanya docker kill selalu meninggalkan Exited (137), dan docker stop pada container yang proses utamanya tidak menangani SIGTERM juga berakhir di sana… sebenarnya tidak — container seperti itu justru berakhir di 143, karena prosesnya tumbang oleh SIGTERM yang sopan itu sendiri, persis seperti yang akan kita saksikan sebentar lagi pada percobaan docker wait.
Exit Code Umum dan Langkah Diagnosis #
Tujuh kode berikut menutupi hampir semua kejadian yang akan kita jumpai sehari-hari. Tabel berikut merangkum artinya beserta langkah diagnosis yang paling produktif untuk masing-masing:
| Kode | Arti | Langkah diagnosis |
|---|---|---|
| 0 | Berhasil — PID 1 menuntaskan tugasnya dengan rapi | Tidak perlu diagnosis; baca docker logs bila ingin kronologinya |
| 1 | Kegagalan aplikasi — program gagal dan melaporkan dirinya | Baca docker logs dan cari pesan error atau exception paling akhir |
| 125 | Perintah ditolak daemon — flag atau nilainya tidak valid | Periksa penulisan perintah; bandingkan dengan --help |
| 126 | Perintah ditemukan tetapi tidak bisa dieksekusi — izin salah | Periksa bit eksekusi berkas dan arsitektur biner image |
| 127 | Perintah tidak ditemukan di dalam image | Periksa ejaan nama biner dan variabel PATH; verifikasi image yang dipakai |
| 137 | Dihentikan SIGKILL — 128+9, termasuk OOM kill | Periksa riwayat docker kill dan masa tenggang stop; periksa OOMKilled |
| 143 | Dihentikan SIGTERM — 128+15 | Umumnya wajar akibat docker stop; pastikan aplikasi menangani sinyal |
Urutan kerja yang sehat ketika sebuah container mati adalah: baca kode keluarnya lebih dulu lewat docker ps -a, duga penyebabnya lewat tabel di atas, lalu gali buktinya di docker logs — exit code mempersempit pencarian, log yang menengahi.
docker wait dan docker inspect #
Dua perintah pelengkap membuat kita bisa bekerja dengan exit code secara langsung. Yang pertama, docker wait, memblokir terminal sampai container yang disebut berhenti, lalu mencetak kode keluarnya. Kita siapkan container korban berumur lima menit — image alpine yang sama, kali ini tanpa --rm supaya bekasnya bisa kita periksa:
$ docker run -d --name uji-tunggu alpine:3.20 sleep 300Output yang Dihasilkan:
143371a70729ccdd11c5d92a68fd5364fd4ef0d23d06ca538f6ad8b776cc386aBuka terminal kedua, lalu jalankan docker wait uji-tunggu di terminal pertama — layar akan diam, menunggu. Dari terminal kedua, kita hentikan uji-tunggu dengan jalur sopan yang sudah kita kenal:
$ docker stop uji-tungguOutput yang Dihasilkan:
uji-tungguBeberapa saat setelah perintah kedua, terminal pertama yang tadi diam akhirnya menjawab:
143Mari kita telaah rangkaian kejadian di atas:
docker waitmemblokir sampai ada yang mati — perintah di terminal pertama tidak mencetak apa pun selamauji-tungguhidup; sesi menunggu berakhir begitu container berhenti, dan satu-satunya keluarannya adalah exit code.143berasal darisleepyang telanjang — programsleeptidak memasang penanganSIGTERM; ketikadocker stopmengirim sinyal tersebut, kernel menghentikan proses begitu saja dan melaporkannya sebagai128 + 15 = 143.- Kontras dengan pengalaman kita pada
web— nginx menangani sinyal berhenti dengan rapi sehinggadocker stop webselalu berakhirExited (0); dua container yang dimatikan dengan perintah yang sama meninggalkan dua angka berbeda, dan bedanya murni soal siapa yang mengurus sinyal.
Status container kini memuat korban baru, lengkap dengan kodenya:
$ docker ps -aOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7c2a91b3e55 nginx "/docker-entrypoint.…" 56 minutes ago Up 34 minutes 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp web
3a1c8f2e9b7d redis:7 "docker-entrypoint.s…" 50 minutes ago Up 50 minutes 6379/tcp cache
143371a70729 alpine:3.20 "sleep 300" 2 minutes ago Exited (143) About a minute ago uji-tungguPerintah pelengkap kedua membaca kode yang sama secara retroaktif — berguna untuk container detached yang sudah lama berlalu atau saat $? tidak pernah sempat kita tangkap. Template Go yang kita kenal dari Chapter docker help dan docker version bekerja di sini:
$ docker inspect --format '{{.State.ExitCode}}' uji-tungguOutput yang Dihasilkan:
143Mari kita telaah pemakaian kedua perintah pelengkap tersebut:
{{.State.ExitCode}}— template membaca fieldExitCodedi dalam bagianStatepada metadata container;docker inspecttanpa--formatakan mencetak seluruh metadata JSON itu dan kolomStatemenyimpan status, waktu mulai, waktu berhenti, hingga penanda OOM.docker waituntuk masa kini,docker inspectuntuk masa lalu —waitmenghadang kita di depan peristiwa yang sedang berjalan, sementarainspectmenggali arsip yang sudah tersimpan; keduanya melengkapi, bukan bersaing.
Bekas percobaan kita bereskan sesuai kebiasaan section ini:
$ docker rm uji-tungguOutput yang Dihasilkan:
uji-tungguMenutup Siklus Latihan #
Sepuluh chapter silam kita memulai dari mesin yang kosong — tabel docker ps yang hanya menampilkan kepala kolom. Kini, setelah menjalankan seluruh siklus hidup container, sudah sepantasnya kita menutup section dengan mengembalikan mesin ke keadaan semula, dan kita lakukan persis dengan urutan yang dianjurkan galat docker rm pada Chapter Menghapus Container: hentikan secara sopan lebih dulu, baru hapus:
$ docker stop web cacheOutput yang Dihasilkan:
web
cache$ docker rm web cacheOutput yang Dihasilkan:
web
cache$ docker ps -aOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESTabel kosong — persis tampilan pertama yang kita saksikan pada Chapter Struktur Perintah Docker. Yang tidak ikut kosong adalah penyimpanan image: nginx, redis:7, alpine:3.20, dan hello-world tetap menunggu di mesin, siap melahirkan container baru kapan pun tanpa satu baris unduhan. Dan di sinilah section ini menutup dirinya dengan satu pertanyaan yang belum pernah kita jawab: selama sepuluh chapter, kita memperlakukan image sebagai kotak hitam yang tinggal kita sebut namanya — padahal struktur internal image menjelaskan mengapa container menyala seketika, mengapa docker rm tidak pernah merusak image asalnya, dan mengapa layer yang sama bisa dipakai bergiliran tanpa disalin. Section berikutnya, Docker Image, membuka kotak itu lapisan demi lapisan — dimulai dari Chapter Konsep Image Layer.
Referensi #
- docker container run — referensi resmi run beserta daftar lengkap exit code 125, 126, dan 127.
- docker container wait — referensi resmi wait untuk memblokir hingga container berhenti.
- docker container inspect
— referensi resmi inspect beserta pemakaian
--format.