Crash loop adalah kegagalan yang paling berisik di luar namun paling sunyi di dalam: container mati, daemon membangkitkannya lewat restart policy, container mati lagi — berulang tanpa satu pun pesan muncul di layar kita. Yang terlihat hanyalah status Restarting (1) yang terus berganti umur di kolom STATUS, sementara jejak lengkapnya tersimpan rapi di tempat lain: log terakhir aplikasi, exit code, jumlah kebangkitan di docker inspect, sampai catatan kernel bila prosesnya dibunuh kelaparan memori. Korban sore ini adalah worker-gambar — service baru di stack toko yang memproses unggahan gambar produk, berjalan dari image worker:1.0 yang sore itu berhasil dibangun setelah chapter sebelumnya. Chapter ini menyusun jejak-jejak tersebut menjadi urutan diagnosis yang tetap, memetakan lima pola penyebab yang paling sering terjadi, lalu menutupnya dengan dua skenario nyata dari laporan sampai pemulihan — termasuk satu keterampilan yang sering dilupakan: menjinakkan restart policy agar akar masalah berhenti bersembunyi.
Mengenali Crash Loop dari Luar #
Pemeriksaan pertama selalu docker ps, dan inilah yang dilihat Budi beberapa menit setelah worker-gambar dideploy:
$ docker ps --filter name=worker-gambarOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c9e2a1b3d4f worker:1.0 "python work…" 25 minutes ago Restarting (1) 3 seconds ago worker-gambarStatus Restarting (1) 3 seconds ago terbaca sebagai: container terakhir mati dengan exit code 1 (angka dalam kurung) dan menghidupkan diri kembali tiga detik yang lalu. Angka dalam kurung itu langsung menghubungkan kasus ini dengan tabel chapter Membaca Container Exit. Restart policy aktif karena service dideklarasikan restart: always; daemon memberi jeda backoff yang membesar antar kebangkitan, namun kebijakan always tidak pernah menyerah. Jumlah kebangkitannya sendiri dicatat terpisah:
$ docker inspect worker-gambar --format 'RestartCount={{.RestartCount}} ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}'Output yang Dihasilkan:
RestartCount=23 ExitCode=1 OOMKilled=falseDua puluh tiga kebangkitan dalam dua puluh lima menit menegaskan loop yang rapat. Exit code 1 yang konsisten di setiap kebangkitan menandakan kegagalan deterministik — bukan gangguan acak — dan OOMKilled=false membebaskan memori dari daftar tersangka sejak awal.
Empat Jejak dalam Urutan Tetap #
Diagnosis crash loop bergerak pada empat jejak berurutan, dari yang termurah menuju yang paling dalam:
docker ps— siapa yang loop, dan dengan exit code berapa: langkah di atas.docker logs— kata-kata terakhir aplikasi sebelum mati.docker inspect— angka-angka (RestartCount,ExitCode,OOMKilled) untuk mengonfirmasi pola.dmesg— catatan kernel, dibuka hanya bila exit code mengarah ke 137.
Jejak kedua pada kasus kita berbunyi:
$ docker logs --tail 6 worker-gambarOutput yang Dihasilkan:
[2026-10-14 15:34:02] worker-gambar 1.0 dimulai
[2026-10-14 15:34:02] membaca konfigurasi dari /app/config/config.yml
Traceback (most recent call last):
File "/app/worker.py", line 28, in <module>
with open('/app/config/config.yml') as f:
FileNotFoundError: [Errno 2] No such file or directory: '/app/config/config.yml'Jejak ketiga sudah kita jalankan di atas dan angkanya konsisten dengan traceback. Jejak keempat tidak diperlukan di kasus ini — dmesg adalah hakim untuk exit 137, dan kita akan memakainya pada varian kedua di bawah.
Pola Penyebab yang Paling Sering #
Lima pola berikut menutup hampir seluruh crash loop yang terjadi di dunia nyata:
| Pola penyebab | Jejak khas | Perbaikan |
|---|---|---|
| Environment variable wajib kosong | KeyError: 'API_TOKEN' atau galat “required setting” lalu exit 1 — kasus laporan-harian kemarin | Isi environment/.env; buat aplikasi gagal cepat dengan pesan jelas |
| Berkas konfigurasi tidak ada | FileNotFoundError pada path yang seharusnya di-mount | Perbaiki pemetaan volume — keluarga masalah chapter Debugging Storage |
| Database belum siap saat start | ECONNREFUSED di awal log lalu mati; sehat saat dites manual | depends_on + service_healthy atau retry di kode — chapter Startup dan Dependency |
| OOM: memory limit kekecilan | exit 137, OOMKilled=true, baris Memory cgroup out of memory di dmesg | Naikkan mem_limit; cari kebocoran dengan docker stats |
| Konflik port di dalam container | address already in use saat startup | Periksa entrypoint yang menyalakan dua proses pada port sama |
Dua pola pertama bersifat deterministik: exit code selalu sama pada setiap kebangkitan. Pola ketiga justru khas intermiten — aplikasi hanya kalah di detik-detik awal, dan begitu database selesai inisialisasi, container yang dibangkitkan ulang berhasil hidup. Pola OOM bisa keduanya: deterministik bila limit memang lebih kecil dari kebutuhan start, atau berkala bila kebocoran perlahan menggembungkan pemakaian memori — kasus lengkapnya sudah kita bedah di chapter Debugging Container pada section Docker dalam Pengembangan.
Skenario Nyata dari Laporan sampai Pemulihan #
Melanjutkan kasus worker-gambar: traceback menunjuk berkas /app/config/config.yml yang tidak ada, padahal service seharusnya menerima berkas itu lewat mount. Karena container sibuk mati-nyala dan exec hanya bisa masuk ke container berstatus Up, konfigurasi mount dibaca dari docker inspect dengan template yang kita kenal dari chapter Docker Inspect:
$ docker inspect worker-gambar --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'Output yang Dihasilkan:
/home/siti/toko/konfig/config.YAML -> /app/config/config.YAMLTersangka tertangkap pada kolom kanan: berkas host bernama config.YAML (Siti menyimpannya dengan ekstensi huruf besar, kebiasaan lama editornya) dipasang ke /app/config/config.YAML — destination ikut menyalin nama berkas host — sedangkan aplikasi membaca /app/config/config.yml. Selisih satu huruf besar-kecil pada ekstensi; bagi Linux yang case-sensitive, keduanya dua nama berkas yang berbeda. FileNotFoundError turun, container mati, daemon membangkitkan, dan siklus berulang dua puluh tiga kali. Perbaikannya satu baris pada compose.yaml — normalisasi destination ke nama yang dibaca aplikasi:
services:
worker-gambar:
image: worker:1.0
volumes:
- ./konfig/config.YAML:/app/config/config.yml
restart: alwaysSetelah docker compose up -d worker-gambar membuat ulang container dengan mount yang benar, verifikasi berjalan dua arah — ke atas dan ke bawah:
$ docker inspect worker-gambar --format 'RestartCount={{.RestartCount}}'Output yang Dihasilkan:
RestartCount=0$ docker logs --tail 3 worker-gambarOutput yang Dihasilkan:
[2026-10-14 15:47:11] worker-gambar 1.0 dimulai
[2026-10-14 15:47:11] konfigurasi /app/config/config.yml berhasil dibaca
[2026-10-14 15:47:12] worker siap — mendengarkan antrean gambarRestartCount yang kembali ke nol adalah bukti container benar-benar baru — penghitung tidak diwarisi antar kebangkitan, melainkan direset saat container dibuat ulang. Log yang menyapa “worker siap” dan antrean gambar yang kembali terproses menutup insiden ini.
Varian Kedua: Dibunuh Kelaparan Memori #
Varian crash loop yang tidak menampilkan traceback sama sekali justru lebih sering membingungkan. Service api-kecil — endpoint ringkas untuk aplikasi kasir dengan mem_limit: 256m — mati setiap setengah jam berstatus Restarting (137), dan log terakhirnya sama sekali normal:
[2026-10-14 16:40:02] permintaan #4812 selesai dalam 312msTidak ada jejak galat — khas proses yang dibunuh dari luar tanpa sempat pamit, sebagaimana kita saksikan pada chapter Membaca Container Exit. Jejak ketiga langsung menjawab: ExitCode=137, OOMKilled=true. Konfirmasi terakhir turun ke catatan kernel:
$ sudo dmesg | grep -i 'out of memory'Output yang Dihasilkan (dipotong):
[10244.882015] Memory cgroup out of memory: Killed process 3120 (python3) total-vm:523468kB, anon-rss:247804kBKernel mengakui pembunuhan itu: cgroup container kehabisan 256 MiB. Perbaikan jangka pendek menaikkan langit-langitnya di compose.yaml:
services:
api-kecil:
image: api-kecil:1.2
mem_limit: 512m
restart: alwaysSetelah container dibuat ulang, kamera resource memverifikasi bahwa aplikasi kini bernapas:
$ docker stats --no-stream api-kecilOutput yang Dihasilkan:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
c50d38e1a9b7 api-kecil 2.14% 298.7MiB / 512MiB 58.34% 1.8MB / 620kB 41.2MB / 0B 9298.7MiB / 512MiB pada MEM USAGE / LIMIT menegaskan kebutuhan aslinya memang melampaui 256 MiB. Menaikkan limit tanpa pengawasan hanya menunda kematian berikutnya: pemakaian yang terus menempel di plafon baru menandakan kebocoran yang harus diperbaiki di kode — pembahasan penuh batas memori, swap, dan keputusan angkanya adalah urusan section Manajemen Resource.
Menjinakkan Restart Policy untuk Melihat Galat #
Satu keterampilan penutup yang membedakan penyelidik sabar dari yang terburu-buru: menghentikan sementara kebangkitan otomatis. Crash loop yang terus berjalan membuat log tergeser-geser dan host sibuk memutar container yang jelas-jelas akan mati lagi:
$ docker update --restart=no worker-gambarOutput yang Dihasilkan:
worker-gambarPerintah docker update --restart=no mengubah kebijakan tanpa membuat ulang container; kegagalan berikutnya menjadi kegagalan terakhir, sehingga galat mentahnya berhenti di tempat dan bisa dibaca dengan tenang lewat rantai empat jejak. Setelah akar masalah ditemukan dan diperbaiki, kembalikan kebijakan yang semestinya — restart: on-failure atau unless-stopped — sesuai peta chapter Restart Policy.
Dari exit code sampai crash loop, sepanjang section ini kita mengumpulkan satu resep per keluarga kegagalan: container mati, jaringan putus, storage bengkak, build gagal, aplikasi berulang mati. Pertanyaan penutupnya berbeda corak: bagaimana merangkai seluruh resep tersebut menjadi kerangka kerja yang bisa diulang pada masalah yang belum pernah kita lihat sama sekali? Chapter penutup section ini, Strategi Diagnostik, menjawabnya dengan lima langkah yang sistematis.
Referensi #
- Referensi docker container inspect
— field
RestartCount,State.ExitCode, danState.OOMKilleduntuk crash loop. - Referensi docker container logs
— flag
--taildan--sincepada log yang menumpuk lintas restart. - Batas Resource Container
— perilaku OOM kill dan arti
mem_limit. - Referensi docker container update — mengubah restart policy pada container yang berjalan.