Masalah jaringan jarang memperkenalkan diri dengan pesan galat yang jelas: sebuah halaman menggantung, 502 dari proxy, atau keluhan “database tidak bisa dihubungi” yang disertai aplikasi yang tampak sehat. Chapter Troubleshooting Network di section Jaringan Container telah melatih gerbang diagnosis dasarnya satu per satu; chapter ini menyusun ulang pengetahuan itu menjadi satu resep berurutan yang bergerak dari dalam container ke luar — mulai dari soket yang didengarkan aplikasi, pemetaan port ke host, uji dari host, resolusi nama antar container, server DNS, firewall, sampai sudut kasus MTU. Arah “dalam ke luar” bukan selera, melainkan statistik: kegagalan konektivitas jauh lebih sering bersumber dari konfigurasi aplikasi dan environment daripada dari kernel jaringan. Sepanjang jalan kita juga membuka kotak alat baru — container debug netshoot yang berbagi namespace jaringan dengan targetnya — lalu menguji seluruh resep pada satu skenario nyata: aplikasi web yang gagal terhubung ke database-nya sendiri.
Resep Tujuh Langkah dari Dalam ke Luar #
Setiap langkah memangkas satu lapisan kemungkinan, dan langkah pertama yang gagal menunjukkan lapisan tempat masalah bersembunyi. Tabel berikut adalah resepnya secara utuh:
| Langkah | Pertanyaan yang dijawab | Perintah inti |
|---|---|---|
| 1 | Apakah aplikasi benar-benar mendengarkan port itu? | docker exec <container> ss -tlnp |
| 2 | Bagaimana pemetaan port ke host? | docker port <container> |
| 3 | Apakah layanan terjangkau dari host? | curl -v localhost:<port-host> |
| 4 | Apakah nama service lain resolve dari dalam container? | docker exec <container> getent hosts <nama> |
| 5 | Server DNS mana yang menjawab pertanyaan itu? | docker exec <container> nslookup <nama> |
| 6 | Apakah firewall memblokir lalu lintas? | sudo iptables -L DOCKER-USER -n -v |
| 7 | Apakah paket besar terpotong jalur MTU kecil? | ping -M do -s 1472 <tujuan> dari netshoot |
Panggung latihan langkah 1 sampai 5 tetap stack toko yang sedang hidup: db (PostgreSQL) di belakang, backend (Node.js) di tengah, proxy (nginx) sebagai satu-satunya pintu yang menerbitkan port host 8080. Mari kita jalankan resepnya.
Membuktikan Titik Dengar dan Pemetaan Port #
Langkah 1 memastikan proses di dalam container sungguh mendengarkan — sebelum kita menuduh jaringan:
$ docker compose exec db ss -tlnpOutput yang Dihasilkan:
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 244 0.0.0.0:5432 0.0.0.0:* users:(("postgres",pid=1,fd=5))
tcp LISTEN 0 244 [::]:5432 [::]:* users:(("postgres",pid=1,fd=6))Alamat 0.0.0.0 dan [::] berarti PostgreSQL mendengarkan di semua antarmuka — container lain boleh mendekat. Jebakan klasik di langkah ini adalah baris pendengar yang hanya menunjuk loopback:
tcp LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=1,fd=18))Baris seperti itu menyatakan aplikasi hanya melayani dirinya sendiri: 127.0.0.1 di dalam container adalah loopback container itu, bukan loopback host — layanan tampak “hidup” namun tak terjangkau dari container mana pun. Perbaikannya ada di konfigurasi aplikasi, bukan di Docker: server Node atau Flask harus mendengarkan 0.0.0.0 — biasanya lewat satu variabel HOST di konfigurasinya. Catatan praktis: image ramping berbasis Alpine kerap tidak memuat ss — apk add iproute2 sekejap di container yang diperiksa adalah jalan cepat, dan pola yang lebih bersih bernama netshoot menunggu beberapa bagian di bawah.
Langkah 2 memeriksa pemetaan port. Untuk db, docker port toko-db-1 kembali tanpa mencetak satu baris pun — dan itu bukan bug: database hanya dikonsumsi container lain di network Compose, jadi tidak ada pintu ke host. Bandingkan dengan proxy yang memang diterbitkan:
$ docker port toko-proxy-1Output yang Dihasilkan:
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080Formatnya “port container -> alamat host”, dan cara membacanya sama dengan kolom PORTS pada docker ps yang kita kenal dari chapter Port Publishing. Langkah 3 menutup sisi host dengan uji langsung:
$ curl -sI http://localhost:8080Output yang Dihasilkan:
HTTP/1.1 200 OK
Server: nginx/1.29.4Dua kalimat merangkum langkah 1 sampai 3: dari host kita selalu memanggil port host, dan dari dalam network selalu port container. Kedua kalimat itulah sumber tertukar paling sering pada laporan “portnya tidak bisa diakses”.
Nama Layanan dan DNS di Baliknya #
Langkah 4 dan 5 berpindah ke dalam network Compose — wilayah tempat keluhan “tidak bisa konek” paling sering bermuara:
$ docker compose exec backend getent hosts dbOutput yang Dihasilkan:
172.19.0.2 dbgetent menempuh jalur resolver sistem — jalur yang sama yang dipakai pustaka pg ketika backend menyambung ke db — sehingga satu baris ini adalah wujud yang dilihat aplikasi sungguhan, pola yang kita kenal dari chapter Service Discovery. Langkah 5 menelusuri satu lapis lebih dalam: server mana yang menjawab pertanyaan itu:
$ docker compose exec backend nslookup dbOutput yang Dihasilkan:
Server: 127.0.0.11
Address: 127.0.0.11:53
Non-authoritative answer:
Name: db
Address: 172.19.0.2Baris Server: 127.0.0.11 adalah tanda tangan embedded DNS Docker — server internal yang hanya aktif pada user-defined network dan menerjemahkan nama service menjadi alamat, lalu meneruskan nama internet ke resolver host; default bridge tidak memilikinya, sebagaimana kita bedah di chapter Container DNS. Pasangan langkah 4-5 juga membedakan dua keluarga gejala: nama gagal terurai berarti masalah DNS — salah ketik, beda network — sedangkan nama terurai tetapi koneksi ditolak berarti masalah soket atau port, dan langkah 1 harus diperiksa ulang.
Firewall dan Chain DOCKER-USER #
Bila langkah 1 sampai 5 sehat namun layanan tetap tak terjangkau dari luar host, tersangka berpindah ke aturan firewall. Docker mengelola aturan iptables/nftables-nya melalui chain DOCKER, dan menyediakan chain DOCKER-USER sebagai tempat aturan milik kita — dievaluasi lebih dulu untuk semua lalu lintas menuju container:
$ sudo iptables -L DOCKER-USER -n -vOutput yang Dihasilkan:
Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
18M 5124M RETURN all -- * * 0.0.0.0/0 0.0.0.0/0Aturan tunggal RETURN adalah wajah sehat: semua paket lolos begitu kembali ke pemrosesan normal. Wajah bermasalah memuat baris tambahan semacam DROP all -- eth0 * 0.0.0.0/0 172.20.0.0/16 — sisa skrip hardening kantor yang memblokir lalu lintas menuju subnet container. Gejalanya khas: koneksi menggantung timeout, bukan ditolak.
MTU dan VPN #
Sudut kasus langkah tujuh jarang terjadi tapi terkenal membingungkan: koneksi TCP tersambung, permintaan kecil berhasil, namun respons besar — handshake TLS, unduhan image — menggantung tanpa galat. Penyebabnya biasanya jalur VPN atau tunnel dengan MTU di bawah 1500: paket besar dibuang di tengah jalan tanpa sempat melaporkan diri. Uji standarnya memakai ping dengan larangan fragmentasi, dijalankan dari netshoot pada network yang dicurigai:
$ ping -M do -s 1472 8.8.8.8Output yang Dihasilkan:
PING 8.8.8.8 (8.8.8.8) 1472(+28) bytes of data.
ping: local error: message too long, mtu=1400Angka 1472 adalah 1500 dikurangi 28 byte header IP dan ICMP. Pesan kernel menyebut MTU jalur yang sebenarnya: 1400. Obatnya membuat network dengan MTU yang sesuai:
$ docker network create --opt com.docker.network.driver.mtu=1400 net-ujiUntuk seluruh bridge bawaan, nilai mtu dapat pula diatur sekali di daemon.json — konfigurasi daemon yang kita bahas pada chapter Konfigurasi Docker Engine.
Kotak Alat Debugging netshoot #
Dua keluhan klasik mendampingi semua langkah di atas: image ramping tidak membawa alat diagnosis, dan kita tidak ingin mencemari image produksi hanya untuk menyelidiki. Keduanya diselesaikan sekaligus oleh image netshoot — kotak alat jaringan berisi ss, dig, curl, tcpdump, dan puluhan lainnya:
$ docker run --rm -it --network container:toko-backend-1 nicolaka/netshootNetshoot juga membawa jurus pamungkas berupa penyadapan paket langsung:
$ docker run --rm --network container:toko-backend-1 nicolaka/netshoot tcpdump -i any -n port 5432Mari kita bedah perintah tersebut:
--network container:toko-backend-1menempatkan penyadap di namespace jaringan backend, sehingga yang tersadap adalah paket yang benar-benar keluar-masuk aplikasi itu.tcpdump -i anymenyadap semua antarmuka sekaligus — di namespace bersama, paket bisa lewateth0maupun loopback.-n port 5432menonaktifkan resolusi nama dan memfilter hanya lalu lintas port 5432, sehingga keluaran tetap pendek dan terbaca.--rmmembuang container penyadap begitu selesai — tidak ada jejak menempel di host.
Satu catatan capability: tcpdump membuka socket paket mentah sehingga menuntut capability NET_RAW — sesuatu yang netshoot terima dari set default Docker. Bila kebiasaan tim menjalankan segala container dengan --cap-drop=ALL (praktik dari chapter Linux Capabilities), tambahkan --cap-add NET_RAW khusus untuk container debug ini.
Skenario Nyata: Web Tidak Bisa Konek ke Db #
Skenario penutup datang dari proyek sampingan Siti: sebuah aplikasi web kecil bernama web — service backend (Node.js) dan db (PostgreSQL) di satu network Compose, ditulis meniru pola stack toko. Keluhannya klasik: “web tidak bisa konek ke db” — halaman produk menampilkan galat, padahal docker compose ps menunjukkan semua service Up. Budi menjalankan resepnya dengan disiplin.
Langkah 1 dan 2 selesai dalam hitungan detik dan sehat: db mendengarkan 5432 di semua antarmuka, dan keluhan ini memang tidak melibatkan port host karena tidak ada satu pun service yang menerbitkan port — semuanya berbicara di dalam network. Kata aplikasi menjadi petunjuk pertama yang lebih tajam:
$ docker compose logs --tail 4 backendOutput yang Dihasilkan:
web-backend-1 | Error: getaddrinfo ENOTFOUND db-toko
web-backend-1 | code: 'ENOTFOUND', syscall: getaddrinfo, hostname: 'db-toko'
web-backend-1 | koneksi database gagal — permintaan /produk dibatalkanHostname tujuan aplikasi adalah db-toko. Langkah 4 menguji nama itu langsung dari dalam container backend:
$ docker compose exec backend getent hosts db-toko
$ echo "kode keluar: $?"Output yang Dihasilkan:
kode keluar: 2getent tidak mencetak satu baris host pun dan keluar dengan kode 2 — nama db-toko tidak dikenal resolver. Bandingkan dengan nama service yang benar:
$ docker compose exec backend getent hosts dbOutput yang Dihasilkan:
172.20.0.3 dbDi sinilah akar masalahnya. Koneksi string backend dirakit lewat interpolasi kosakata chapter Environment dan env File — nilai dari .env menimpa bawaan ${DB_HOST:-db} — dan Siti menyalin .env dari proyek lamanya yang databasenya memang bernama db-toko, tanpa menyadari service di compose.yaml proyek baru ini bernama db. Resolusi nama gagal sebelum satu paket pun terkirim; tidak ada aturan firewall atau MTU yang bisa disalahkan. Perbaikannya satu baris di .env (DB_HOST=db) disusul docker compose up -d backend untuk membuat ulang container dengan environment yang benar.
Output yang Dihasilkan:
[+] Running 2/2
✔ Container web-db-1 Running 0.0s
✔ Container web-backend-1 Recreated
✔ Container web-backend-1 StartedVerifikasi penutup berjalan dua arah: getent hosts db dari container baru tetap menjawab 172.20.0.3, dan log backend kembali menyapa sehat — terhubung ke database web. Keluhan “tidak bisa konek” ternyata tidak pernah sampai ke lapisan koneksi; nama tujuannya saja yang keliru.
Resep Ringkas dalam Satu Tabel #
Seluruh resep tersebut bisa digenggam dalam satu tabel saku — langkah, perintah, dan wajah sehat yang kita harapkan:
| Langkah | Perintah | Wajah sehat |
|---|---|---|
| 1 | ss -tlnp di dalam container | mendengarkan 0.0.0.0:<port>, bukan 127.0.0.1 |
| 2 | docker port <container> | pemetaan tampil sesuai deklarasi; kosong memang wajar bagi service internal |
| 3 | curl dari host ke port host | kode status 200 |
| 4 | getent hosts <nama> | satu baris IP nama; kosong berarti masalah nama |
| 5 | nslookup <nama> | baris Server: 127.0.0.11 — embedded DNS bekerja |
| 6 | iptables -L DOCKER-USER | aturan RETURN tanpa DROP misterius |
| 7 | ping -M do -s 1472 | tidak ada message too long, mtu=<kecil> |
Jaringan kembali tersambung, tetapi gejala “tidak bisa” tidak selalu bermuara ke jaringan. Kalimat no space left on device, Permission denied, dan “datanya hilang!” tinggal satu lapis di bawahnya: storage. Chapter Debugging Storage membongkar lima keluarga masalah penyimpanan tersebut beserta alat ukurnya.
Referensi #
- Dokumentasi Jaringan Docker
— perilaku DNS embedded, pemetaan port, dan rantai
DOCKER-USER. - Referensi docker container exec — menjalankan perintah diagnosis di dalam container yang hidup.
- Referensi docker container port — membaca pemetaan port container.
- Referensi docker network create
— opsi driver
com.docker.network.driver.mtu.