Container yang gagal start, aplikasi yang tiba-tiba tidak bisa diakses, atau service yang mendadak lambat adalah kejadian sehari-hari begitu Docker dipakai lebih dari sekadar eksperimen lokal. Bedanya Sysadmin/DevOps Engineer yang cekatan dengan yang panik di depan insiden production biasanya bukan soal siapa yang lebih hafal perintah Docker, melainkan siapa yang tahu urutan pemeriksaan yang tepat: dari status container, isi log, konfigurasi jaringan, sampai pemakaian resource. Bab ini membahas alur diagnosis untuk empat kategori masalah yang paling sering ditemui di lapangan, yaitu container yang gagal start, proses di dalam container yang perlu di-debug langsung, gangguan jaringan antar container, dan masalah performa akibat resource yang terbatas.
40.1 Container Gagal Start atau Langsung Berhenti
Masalah paling dasar yang hampir pasti pernah dialami setiap Developer maupun Sysadmin/DevOps Engineer adalah container yang statusnya langsung Exited begitu dijalankan, padahal image yang sama sebelumnya berjalan normal. Bagian ini membahas cara membaca exit code container dan menelusuri penyebabnya lewat log serta hasil inspeksi container.
40.1.1 Membaca Exit Code dan Status Container
Setiap container yang berhenti selalu meninggalkan exit code, angka yang menunjukkan alasan proses utama di dalamnya selesai. Menurut dokumentasi resmi Docker Engine, beberapa exit code punya arti khusus di luar kode yang dikembalikan proses aplikasi itu sendiri: 125 berarti Docker daemon sendiri gagal menjalankan perintah docker run (misalnya opsi yang tidak valid), 126 berarti perintah yang ditentukan ditemukan tapi tidak bisa dieksekusi (masalah permission atau binary yang salah format), dan 127 berarti perintah yang ditentukan tidak ditemukan sama sekali di dalam image.
Cek exit code container yang sudah berhenti lewat docker ps -a terlebih dahulu untuk melihat status singkatnya.
docker ps -a --filter "name=web-app"CONTAINER ID IMAGE STATUS NAMES
a1b2c3d4e5f6 web-app Exited (1) 2 minutes ago web-appUntuk detail exit code yang lebih presisi lewat skrip atau automation, ambil langsung dari docker inspect.
docker inspect web-app --format '{{.State.ExitCode}}'Exit code 0 menandakan proses utama container berhenti normal (bukan error), sementara nilai lain umumnya diteruskan langsung dari kode keluar aplikasi di dalam container. Dua nilai yang sering bikin bingung karena tidak mencerminkan error dari aplikasi itu sendiri adalah 137 dan 143: keduanya adalah hasil penjumlahan 128 + nomor sinyal, di mana 137 berarti proses menerima SIGKILL (sinyal 9, biasanya karena dihentikan paksa lewat docker kill atau kehabisan memori) dan 143 berarti proses menerima SIGTERM (sinyal 15, sinyal berhenti normal yang dikirim docker stop).
40.1.2 Menelusuri Penyebab Lewat Logs dan Inspect
Exit code memberi petunjuk kategori masalah, tapi detail penyebab sebenarnya hampir selalu ada di dalam log container. Tampilkan log lengkap container yang sudah berhenti dengan menambahkan opsi --tail supaya output tidak membanjiri terminal untuk container yang sudah berjalan lama sebelumnya.
docker logs --tail 50 web-appKalau log tidak memberi informasi cukup jelas, misalnya container berhenti sebelum sempat menulis log apa pun, periksa detail konfigurasi dan riwayat state container lewat docker inspect secara menyeluruh, khususnya bagian State dan Config.
docker inspect web-appPerhatikan field State.Error yang kadang berisi pesan error dari Docker daemon sendiri (misalnya kegagalan memasang volume), dan field Config.Cmd serta Config.Entrypoint untuk memastikan perintah yang dijalankan container sudah sesuai ekspektasi. Penyebab paling umum di lapangan untuk container yang langsung Exited adalah proses utama container yang memang didesain berjalan sekali lalu keluar (bukan proses long-running seperti web server), environment variable wajib yang belum di-set sehingga aplikasi gagal saat startup, atau file konfigurasi yang di-mount lewat bind mount ternyata tidak ditemukan di path yang diharapkan di dalam container.
40.2 Debugging Proses di Dalam Container
Sebagian masalah tidak muncul di log dan baru kelihatan setelah menelusuri langsung proses yang berjalan di dalam container, misalnya file konfigurasi yang formatnya salah atau proses yang hang tanpa pernah menuliskan error. Bagian ini membahas cara masuk ke container yang sedang berjalan dan cara mendebug container berbasis image minimal yang tidak punya shell sama sekali.
40.2.1 Masuk ke Container yang Sedang Berjalan
Cara paling langsung memeriksa kondisi di dalam container yang masih berjalan adalah membuka sesi shell interaktif lewat docker exec.
docker exec -it web-app shKalau image yang dipakai menyediakan Bash, ganti sh dengan bash untuk pengalaman shell yang lebih lengkap. Setelah masuk, periksa hal-hal dasar yang sering jadi biang masalah: isi file konfigurasi lewat cat, environment variable yang benar-benar aktif lewat env, serta proses yang sedang berjalan lewat ps aux kalau paket procps tersedia di image tersebut.
docker exec web-app envBandingkan output environment variable ini dengan nilai yang diharapkan aplikasi. Di lapangan, kesalahan sepele seperti nama variable yang typo atau nilai yang tertukar antar environment (development vs production) jauh lebih sering jadi penyebab masalah dibanding bug pada kode aplikasi itu sendiri.
40.2.2 Debugging Container Minimal Tanpa Shell
Image minimal seperti distroless atau image berbasis scratch sengaja tidak menyertakan shell maupun package manager demi memperkecil attack surface dan ukuran image, sesuai praktik keamanan image yang sudah dibahas di bab tersendiri. Konsekuensinya, docker exec -it CONTAINER sh akan gagal dengan pesan seperti OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown.
Solusi paling universal untuk kasus ini adalah menjalankan container debug terpisah yang berbagi namespace proses dan jaringan dengan container target, tanpa perlu mengubah image aslinya sama sekali.
docker run -it --rm --pid=container:web-app --network=container:web-app nicolaka/netshoot shPerintah di atas menjalankan container baru dari image nicolaka/netshoot (image komunitas yang populer untuk keperluan troubleshooting, berisi kumpulan tool jaringan dan diagnosis) yang berbagi PID namespace dan network namespace dengan container web-app. Dari dalam container debug ini, proses container target tetap terlihat lewat ps aux meski shell-nya berjalan di container terpisah, dan trafik jaringan yang diamati (misalnya lewat tcpdump atau curl) persis sama seperti kalau dijalankan langsung di dalam container target.
Docker Desktop versi terkini juga menyediakan perintah docker debug yang mengotomasi teknik serupa lewat satu perintah, lengkap dengan toolbox debugging bawaan, tapi fitur ini menuntut Docker Desktop dengan akun Docker yang aktif berlangganan paket Pro, Team, atau Business, dan tidak tersedia untuk instalasi Docker Engine standalone tanpa Docker Desktop. Untuk server Linux production yang cuma menjalankan Docker Engine tanpa Docker Desktop, situasi paling umum di lapangan, teknik berbagi namespace lewat --pid=container: dan --network=container: di atas tetap jadi pendekatan yang paling portable karena tidak bergantung pada lisensi atau tool tambahan di luar Docker Engine itu sendiri.
40.3 Troubleshooting Jaringan Container
Container yang tidak bisa saling terhubung atau service yang tidak bisa diakses dari luar adalah kategori masalah yang paling sering bikin frustrasi karena gejalanya kelihatan sederhana ("connection refused") tapi penyebabnya bisa beragam. Bagian ini membahas urutan pemeriksaan untuk tiga skenario jaringan yang paling umum: container tidak bisa saling terhubung, port tidak bisa diakses dari host, dan kegagalan resolusi DNS antar container.
40.3.1 Container Tidak Bisa Saling Terhubung
Penyebab paling umum dua container gagal saling terhubung adalah keduanya ternyata tidak berada di network Docker yang sama. Periksa dulu network apa saja yang terpasang ke masing-masing container.
docker inspect web-app --format '{{range $net, $conf := .NetworkSettings.Networks}}{{$net}} {{end}}'Jalankan perintah yang sama untuk container tujuan, lalu bandingkan hasilnya. Kalau kedua container ternyata memakai network default bridge bawaan Docker, ingat bahwa network ini tidak mendukung resolusi nama container secara otomatis; hanya user-defined network (dibuat lewat docker network create atau otomatis oleh Docker Compose) yang menyediakan DNS resolution antar container berdasarkan nama service-nya. Solusinya, pindahkan kedua container ke network yang sama.
docker network connect app-network web-appSetelah dipastikan berada di network yang sama, verifikasi konektivitas lewat percobaan koneksi langsung dari salah satu container ke container lainnya menggunakan nama container sebagai host.
docker exec web-app ping -c 3 dbKalau ping tidak tersedia di image tersebut, gunakan container debug sementara dari image nicolaka/netshoot yang sudah dibahas sebelumnya, dijalankan di network yang sama, untuk menguji konektivitas tanpa perlu menambah tool ke image production.
40.3.2 Port Tidak Bisa Diakses dari Host
Kalau aplikasi di dalam container bisa diakses lewat docker exec tapi tidak bisa diakses dari host, penyebab paling sering adalah kebingungan antara EXPOSE di Dockerfile dengan opsi -p/--publish saat docker run. EXPOSE hanya berfungsi sebagai dokumentasi port yang dipakai aplikasi dan memudahkan link antar container, bukan mempublikasikan port tersebut ke host; publikasi port ke host wajib eksplisit lewat -p.
docker ps --filter "name=web-app" --format "table {{.Names}}\t{{.Ports}}"NAMES PORTS
web-app 0.0.0.0:8080->80/tcpKalau kolom PORTS kosong sama sekali, berarti container memang dijalankan tanpa opsi -p sehingga tidak ada port yang dipublikasikan ke host. Kalau kolom tersebut menunjukkan 127.0.0.1:8080->80/tcp alih-alih 0.0.0.0:8080->80/tcp, port memang sudah dipublikasikan tapi hanya bisa diakses dari localhost host itu sendiri, bukan dari jaringan luar; ini biasanya hasil eksplisit dari opsi -p 127.0.0.1:8080:80. Kalau publikasi port sudah benar tapi akses tetap gagal dari luar host, periksa juga aturan firewall di level OS host (misalnya ufw atau firewalld di Linux), karena Docker memodifikasi aturan iptables sendiri untuk port mapping tapi tetap bisa terhalang firewall tambahan yang berjalan di atasnya.
40.3.3 Masalah DNS Resolution di Dalam Container
Docker Engine menjalankan embedded DNS server internal di alamat 127.0.0.11 untuk setiap user-defined network, yang bertugas menerjemahkan nama container atau nama service Compose jadi alamat IP di dalam network yang sama. Kalau container gagal me-resolve nama container lain padahal sudah berada di network yang sama, periksa dulu isi /etc/resolv.conf di dalam container untuk memastikan DNS server yang dipakai memang mengarah ke DNS internal Docker.
docker exec web-app cat /etc/resolv.confKalau isi file tersebut sudah benar tapi resolusi tetap gagal, uji langsung dengan tool nslookup atau dig dari dalam container (atau lewat container debug nicolaka/netshoot kalau image target tidak menyediakan tool tersebut).
docker exec web-app nslookup dbKegagalan resolusi DNS untuk nama container yang benar-benar aktif dan berada di network yang sama biasanya berujung pada dua kemungkinan: nama yang dipakai salah ketik (DNS resolution di Docker berbasis nama container atau network alias, sensitif terhadap penulisan persis), atau container yang dituju baru saja di-restart sehingga sempat mendapat IP baru namun cache DNS di sisi aplikasi pemanggil belum diperbarui. Untuk kasus kedua, pastikan aplikasi melakukan resolusi DNS ulang tiap kali membuka koneksi baru, bukan meng-cache hasil resolusi IP secara permanen di memori proses.
40.4 Debugging Performa Container
Container yang berjalan tanpa error tapi terasa lambat, atau tiba-tiba mati sendiri tanpa pesan error yang jelas dari aplikasi, biasanya mengarah ke keterbatasan resource host atau limit yang diterapkan pada container itu sendiri. Bagian ini membahas cara memantau pemakaian resource container secara real-time, mendiagnosis container yang mati karena kehabisan memori, dan menelusuri bottleneck pada operasi disk I/O.
40.4.1 Monitoring Resource Usage dengan docker stats
Perintah docker stats menampilkan pemakaian CPU, memori, jaringan, dan disk I/O seluruh container yang sedang berjalan secara real-time, mirip fungsi top tapi khusus level container.
docker stats --no-streamCONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a1b2c3d4e5f6 web-app 145.32% 890.5MiB / 1GiB 86.96% 1.2MB / 850kB 12MB / 4MB 24Opsi --no-stream mengambil satu snapshot saja alih-alih terus memperbarui tampilan, cocok dipakai dalam skrip monitoring atau saat sekadar butuh cek cepat sekali lihat. Perhatikan kolom CPU % yang bisa melebihi 100% pada host dengan banyak core, karena angka tersebut dihitung relatif terhadap satu core; nilai 145.32% pada contoh di atas berarti container memakai setara 1,45 core CPU. Kolom MEM USAGE / LIMIT penting diperhatikan bersamaan: kalau MEM % konsisten mendekati 100%, container berisiko besar dimatikan paksa oleh kernel OOM killer kapan saja. Kolom PIDS di ujung kanan juga layak dipantau, karena jumlah proses/thread yang melonjak tiba-tiba biasanya menandakan aplikasi gagal membersihkan proses child-nya (process leak), bukan sekadar lonjakan trafik normal.
40.4.2 Container Dimatikan Paksa karena OOM
Ketika sebuah container melebihi batas memori yang diberikan lewat opsi --memory, kernel Linux akan mengaktifkan OOM killer (Out Of Memory killer) untuk menghentikan paksa proses di dalam container tersebut lewat sinyal SIGKILL, menghasilkan exit code 137 seperti yang sudah dibahas sebelumnya. Pastikan dulu apakah exit code 137 memang disebabkan OOM, bukan sekadar docker kill manual, lewat field OOMKilled di hasil docker inspect.
docker inspect web-app --format '{{.State.OOMKilled}}'Nilai true pada output di atas memastikan container memang dihentikan karena kehabisan memori sesuai limit yang diberikan. Solusi jangka pendek adalah menaikkan limit memori container lewat opsi --memory supaya cukup menampung kebutuhan aplikasi pada beban puncak, tapi solusi jangka panjang yang lebih tepat adalah menelusuri apakah pemakaian memori aplikasi memang wajar atau ada indikasi memory leak yang membuat pemakaian terus naik seiring waktu tanpa pernah turun kembali. Pantau tren pemakaian memori container tersebut selama beberapa siklus operasi normal lewat docker stats untuk membedakan kedua kemungkinan ini; pemakaian yang naik terus tanpa pernah stabil biasanya ciri khas memory leak, sementara pemakaian yang naik-turun mengikuti pola trafik biasanya memang cuma butuh limit yang lebih longgar.
40.4.3 Mendiagnosis Bottleneck I/O Disk
Container yang terasa lambat pada operasi baca/tulis file, meski CPU dan memori masih longgar, biasanya mengarah ke bottleneck pada disk I/O. Kolom BLOCK I/O pada output docker stats memberi gambaran kasar volume data yang dibaca dan ditulis container, tapi untuk detail lebih dalam soal kecepatan I/O di level host, gunakan tool standar Linux seperti iostat (bagian dari paket sysstat, terpasang lewat apt install sysstat di Debian/Ubuntu atau dnf install sysstat di distribusi berbasis RHEL) yang dijalankan langsung di host, bukan di dalam container.
iostat -x 2Perhatikan kolom %util pada output iostat; nilai yang konsisten mendekati 100% pada disk tertentu menandakan disk tersebut memang sudah jadi bottleneck, terlepas dari container mana pun yang membebaninya. Perlu diingat, perintah iostat ini mengasumsikan host Linux asli. Kalau Docker dijalankan lewat Docker Desktop di macOS atau Windows, seluruh container sebenarnya berjalan di dalam satu virtual machine Linux internal yang dikelola Docker Desktop, sehingga menjalankan iostat langsung di terminal macOS/Windows tidak akan mencerminkan I/O disk container sama sekali; pemantauan performa di kedua OS ini lebih praktis lewat dashboard resource bawaan Docker Desktop, bukan lewat tool command-line Linux semacam ini.
Penyebab paling umum bottleneck I/O yang spesifik pada container di lapangan adalah aplikasi yang menulis banyak data ke writable layer container (bukan ke volume terpisah), karena storage driver seperti overlay2 punya overhead tambahan dibanding menulis langsung ke volume yang di-mount. Pindahkan direktori yang menampung data bervolume tinggi (log aplikasi, file upload, direktori data database) ke named volume alih-alih dibiarkan menumpuk di writable layer container, sesuai praktik pengelolaan data yang sudah dibahas pada bab volume Docker, supaya performa I/O lebih stabil dan tidak ikut hilang begitu container dihapus.
40.5 Ringkasan Gejala dan Penyebab Umum
Tabel berikut merangkum gejala, kemungkinan penyebab, dan langkah diagnosis awal untuk masalah-masalah yang sudah dibahas di bab ini, sebagai referensi cepat saat menghadapi insiden serupa di lapangan.
| Gejala | Kemungkinan Penyebab | Langkah Diagnosis Awal |
|---|---|---|
Container langsung Exited setelah start | Proses utama bukan long-running, env variable hilang, file config tidak ditemukan | docker logs dan docker inspect pada field State dan Config |
Exit code 137 | Dihentikan paksa (SIGKILL), sering karena OOM | docker inspect --format '{{.State.OOMKilled}}' |
| Dua container tidak bisa saling terhubung | Berada di network Docker yang berbeda | Bandingkan NetworkSettings.Networks tiap container |
| Port tidak bisa diakses dari host | EXPOSE tanpa -p, atau publish hanya ke 127.0.0.1 | docker ps --format "table {{.Names}}\t{{.Ports}}" |
| Resolusi nama container gagal | Memakai default bridge network, bukan user-defined network | docker exec CONTAINER cat /etc/resolv.conf dan nslookup |
| Aplikasi terasa lambat, resource host longgar | Bottleneck disk I/O pada writable layer container | docker stats kolom BLOCK I/O dan iostat -x di host |
Sebagian besar insiden Docker di production sebenarnya berujung pada kombinasi kecil dari daftar di atas, bukan masalah eksotis yang butuh riset mendalam. Kebiasaan memeriksa exit code, log, konfigurasi network, dan resource usage secara berurutan, sebelum buru-buru mencurigai bug pada kode aplikasi, biasanya sudah cukup mempersempit sebagian besar masalah Docker ke penyebab yang jelas dan solusi yang konkret.

