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:

LangkahPertanyaan yang dijawabPerintah inti
1Apakah aplikasi benar-benar mendengarkan port itu?docker exec <container> ss -tlnp
2Bagaimana pemetaan port ke host?docker port <container>
3Apakah layanan terjangkau dari host?curl -v localhost:<port-host>
4Apakah nama service lain resolve dari dalam container?docker exec <container> getent hosts <nama>
5Server DNS mana yang menjawab pertanyaan itu?docker exec <container> nslookup <nama>
6Apakah firewall memblokir lalu lintas?sudo iptables -L DOCKER-USER -n -v
7Apakah 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:

bash
$ docker compose exec db ss -tlnp

Output yang Dihasilkan:

text
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:

text
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:

bash
$ docker port toko-proxy-1

Output yang Dihasilkan:

text
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080

Formatnya “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:

bash
$ curl -sI http://localhost:8080

Output yang Dihasilkan:

text
HTTP/1.1 200 OK
Server: nginx/1.29.4

Dua 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:

bash
$ docker compose exec backend getent hosts db

Output yang Dihasilkan:

text
172.19.0.2      db

getent 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:

bash
$ docker compose exec backend nslookup db

Output yang Dihasilkan:

text
Server:         127.0.0.11
Address:        127.0.0.11:53

Non-authoritative answer:
Name:   db
Address: 172.19.0.2

Baris 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:

bash
$ sudo iptables -L DOCKER-USER -n -v

Output yang Dihasilkan:

text
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/0

Aturan 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:

bash
$ ping -M do -s 1472 8.8.8.8

Output yang Dihasilkan:

text
PING 8.8.8.8 (8.8.8.8) 1472(+28) bytes of data.
ping: local error: message too long, mtu=1400

Angka 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:

bash
$ docker network create --opt com.docker.network.driver.mtu=1400 net-uji

Untuk 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:

bash
$ docker run --rm -it --network container:toko-backend-1 nicolaka/netshoot

Netshoot juga membawa jurus pamungkas berupa penyadapan paket langsung:

bash
$ docker run --rm --network container:toko-backend-1 nicolaka/netshoot tcpdump -i any -n port 5432

Mari kita bedah perintah tersebut:

  1. --network container:toko-backend-1 menempatkan penyadap di namespace jaringan backend, sehingga yang tersadap adalah paket yang benar-benar keluar-masuk aplikasi itu.
  2. tcpdump -i any menyadap semua antarmuka sekaligus — di namespace bersama, paket bisa lewat eth0 maupun loopback.
  3. -n port 5432 menonaktifkan resolusi nama dan memfilter hanya lalu lintas port 5432, sehingga keluaran tetap pendek dan terbaca.
  4. --rm membuang 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:

bash
$ docker compose logs --tail 4 backend

Output yang Dihasilkan:

text
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 dibatalkan

Hostname tujuan aplikasi adalah db-toko. Langkah 4 menguji nama itu langsung dari dalam container backend:

bash
$ docker compose exec backend getent hosts db-toko
$ echo "kode keluar: $?"

Output yang Dihasilkan:

text
kode keluar: 2

getent tidak mencetak satu baris host pun dan keluar dengan kode 2 — nama db-toko tidak dikenal resolver. Bandingkan dengan nama service yang benar:

bash
$ docker compose exec backend getent hosts db

Output yang Dihasilkan:

text
172.20.0.3      db

Di 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:

text
[+] Running 2/2
 ✔ Container web-db-1        Running    0.0s
 ✔ Container web-backend-1   Recreated
 ✔ Container web-backend-1   Started

Verifikasi 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:

LangkahPerintahWajah sehat
1ss -tlnp di dalam containermendengarkan 0.0.0.0:<port>, bukan 127.0.0.1
2docker port <container>pemetaan tampil sesuai deklarasi; kosong memang wajar bagi service internal
3curl dari host ke port hostkode status 200
4getent hosts <nama>satu baris IP nama; kosong berarti masalah nama
5nslookup <nama>baris Server: 127.0.0.11 — embedded DNS bekerja
6iptables -L DOCKER-USERaturan RETURN tanpa DROP misterius
7ping -M do -s 1472tidak 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 #