Mesin nama Docker kini sudah terbongkar tuntas: satu alamat loopback 127.0.0.11, dua sumber jawaban, nama pendek sebagai kontrak, dan alias sebagai nama tambahan. Tetapi memahami mesinnya belum sama dengan mengemudikannya — aplikasi sungguhan tidak saling menyapa dengan ping, melainkan dengan HTTP ke web server dan koneksi TCP ke database. Chapter ini merangkai keduanya menjadi satu alur kerja utuh: sebuah studi kasus tiga container yang saling bekerja lewat nama container tanpa satu pun alamat IP yang kita hafal, diuji dari sisi aplikasi memakai curl dan nc, lalu ditutup dengan arah yang belum pernah kita tempuh — dari container menuju service yang berjalan di mesin host, lengkap dengan demo host.docker.internal dan gateway yang dijanjikan pada Chapter Container DNS.

Menyiapkan Studi Kasus net-latihan #

Panggung kita bangun dari jaringan baru bernama net-latihan — net-aplikasi sengaja kita biarkan berdiri kosong, sehingga pool berikutnya jatuh ke 172.19.0.0/16. Tiga container menempatinya:

bash
$ docker network create net-latihan
$ docker run -d --name web --network net-latihan nginx
$ docker run -d --name db --network net-latihan -e POSTGRES_PASSWORD=rahasia123 postgres:18-alpine
$ docker run -d --name app --network net-latihan alpine:3.20 sleep 1h

Output yang Dihasilkan:

text
b4a7e2c9f1d53680adb4f7c2e91d5a03f6c8e2b7d9a4f1c5e3b8d6a0f4c7e2b91
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a
3d7f1a9c5e8b2f6a0c4d9e1b7f3a5c8d6e0b2f9c4a7d5f1e8b3c6a9d0f4e2b5c7
8a2e6c1f9b4d7a3e5c0f8d2b6e9a4c7f1d5b8e3a6c0f9d2b5e8a4c7f1d6b9e3a5c

Mari kita telaah peran masing-masing container:

  1. web — web server nginx sebagai contoh service HTTP. Portnya sengaja tidak kita terbitkan dengan -p karena hanya sesama penghuni net-latihan yang perlu mengunjunginya; pembukaan pintu ke dunia luar adalah cerita chapter berikutnya.
  2. db — database PostgreSQL dengan password fiktif rahasia123, mendengarkan port 5432 di dalam jaringan. Ia mewakili service yang diakses lewat koneksi TCP murni, bukan HTTP.
  3. app — container Alpine polos yang berperan sebagai aplikasi. Perintah sleep 1h sekadar menjaganya hidup selama satu jam agar bisa kita kirim perintah lewat docker exec.

Pembagian alamat berjalan sesuai ramalan pool: gateway 172.19.0.1 milik host, web menerima 172.19.0.2, db menerima 172.19.0.3, dan app menerima 172.19.0.4. Angka-angka itu tidak perlu kita hafal — dan justru itulah inti chapter ini.

Memasang Perkakas Uji sekaligus Membuktikan Internet #

Container app baru menyimpan busybox; curl dan nc belum tersedia. Kita pasang keduanya dari dalam container:

bash
$ docker exec app apk add curl netcat-openbsd

Output yang Dihasilkan (dipotong):

text
fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
(1/7) Installing ca-certificates-bundle (20240226-r0)
...
OK: 9 MiB in 22 packages

Perintah ini sekaligus membayar satu utang bukti dari Chapter Container DNS: unduhan paket dari repositori publik berhasil tanpa satu baris konfigurasi — pertanyaan DNS dl-cdn.alpinelinux.org dikirim ke 127.0.0.11, diteruskan daemon ke DNS upstream host, lalu paket mengalir masuk lewat NAT jaringan bridge. Kedua jalur yang dulu hanya kita baca sebagai diagram kini bekerja untuk kita. Perhatikan juga bahwa nc dipasang dari paket netcat-openbsd, bukan busybox nc bawaan Alpine, karena varian inilah yang mengenal mode pemeriksaan -zv yang akan kita pakai.

Memanggil web dengan curl #

Momen pertama percakapan antar container: app mengakses web berdasarkan namanya, tanpa satu angka IP:

bash
$ docker exec app curl -s http://web

Output yang Dihasilkan (dipotong):

text
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light; }
body { width: 35em; margin: 0 auto;
...

Satu baris perintah itu menuntaskan rantai panjang yang kini bisa kita bedah lengkap:

  1. curl membaca /etc/resolv.conf dan menemukan nameserver 127.0.0.11 — pintu embedded DNS yang hidup di dalam network namespace app.
  2. Pertanyaan “web” dijawab dari tabel internal daemon — bukan dari internet — dengan alamat 172.19.0.2; nama pendek satu label memang wilayah tabel itu.
  3. curl membuka koneksi TCP ke 172.19.0.2 port 80 — paket bergerak lewat switch virtual net-latihan tanpa NAT dan tanpa keluar host, sehingga latensinya serendah hasil ping pada chapter sebelumnya.
  4. nginx menjawab dengan halaman selamat datang — service HTTP yang tidak pernah kita terbitkan ke host tetap sepenuh hidup bagi sesama penghuni jaringan; -p memang urusan dunia luar, bukan urusan tetangga.

Menguji Port TCP dengan nc #

Web server dijawab lewat HTTP; database berkomunikasi lewat TCP pada port 5432. Netcat mode pemeriksaan menguji keberadaan port itu tanpa berbicara protokol PostgreSQL:

bash
$ docker exec app nc -zv db 5432

Output yang Dihasilkan:

text
Connection to db 172.19.0.3 5432 port [tcp/*] succeeded!

Kalimat succeeded! menyatakan tiga hal sekaligus: nama db berhasil diterjemahkan menjadi 172.19.0.3, koneksi TCP ke port 5432 berhasil dibuka, dan PostgreSQL benar-benar mendengarkan di sana. Mode -zv langsung menutup koneksi tanpa mengirim data — dirancang khusus untuk menjawab pertanyaan “port ini hidup atau tidak”. Dua galat yang mungkin muncul membawa makna yang berbeda dan menuntut penanganan yang berbeda:

  1. Connection refused berarti container tujuan hidup dan dapat dijangkau, tetapi tidak ada proses yang mendengarkan port tersebut — layanannya belum selesai menyala, atau portnya memang salah. Arah penelusuran: ke dalam container tujuan.
  2. Kegagalan resolusi nama — bad address atau Name does not resolve berarti pertanyaan DNS tidak terjawab, biasanya karena container pemanggil dan container tujuan berada di jaringan yang berbeda. Arah penelusuran: keanggotaan network, bukan aplikasi.

Membedakan dua wajah kegagalan ini adalah keterampilan inti troubleshooting jaringan container — kelima resep lengkapnya menunggu chapter penutup section ini.

Aturan Utama: Nama, Bukan IP #

Dua percobaan di atas sama-sama memakai nama container sebagai alamat, dan itu bukan gaya penulisan melainkan aturan praktis: nama kita yang menentukan sendiri, jauh lebih stabil daripada alamat IP yang dibagikan ulang setiap kali container diciptakan.

Bukti terbaiknya adalah membuat ulang container db itu sendiri, lalu memanggil dengan perintah yang persis sama:

bash
$ docker rm -f db
$ docker run -d --name db --network net-latihan -e POSTGRES_PASSWORD=rahasia123 postgres:18-alpine
$ docker exec app nc -zv db 5432

Output yang Dihasilkan:

text
db
c9f4b7e2a1d53860fcb2e7a4d91c5f0a3e8d6b2c9f4a7e1d5b8c3a6f0e2d9b4c7a
Connection to db 172.19.0.5 5432 port [tcp/*] succeeded!

Alamat db berpindah dari 172.19.0.3 ke 172.19.0.5, dan tidak satu pun perintah kita perlu berubah — embedded DNS selalu mengarahkan nama ke alamat yang sedang berlaku.

Dari Container ke Host #

Satu arah percakapan masih tersisa: container yang hendak menjangkau service di mesin host — misalnya API development yang berjalan native di laptop pada port 8000. Di Docker Desktop, nama istimewa host.docker.internal tersedia bawaan: dari container Siti, curl http://host.docker.internal:8000 tiba di proses host tanpa konfigurasi apa pun. Di Linux, dua jalur lain menyambut Budi. Jalur pertama: setiap network punya gateway — 172.19.0.1 untuk net-latihan — dan gateway adalah wajah host di jaringan itu:

bash
$ docker exec app ping -c 2 172.19.0.1

Output yang Dihasilkan:

text
PING 172.19.0.1 (172.19.0.1): 56 data bytes
64 bytes from 172.19.0.1: seq=0 ttl=64 time=0.119 ms
64 bytes from 172.19.0.1: seq=1 ttl=64 time=0.104 ms

--- 172.19.0.1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss

Jalur kedua memberi Linux nama yang sama dengan Docker Desktop lewat flag --add-host. Nilai istimewa host-gateway digantikan daemon dengan alamat gateway host saat container diciptakan:

bash
$ docker run --rm --add-host=host.docker.internal:host-gateway alpine:3.20 ping -c 2 host.docker.internal

Output yang Dihasilkan:

text
PING host.docker.internal (172.17.0.1): 56 data bytes
64 bytes from 172.17.0.1: seq=0 ttl=64 time=0.131 ms
64 bytes from 172.17.0.1: seq=1 ttl=64 time=0.118 ms

--- host.docker.internal ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss

Perhatikan alamat yang dijawab: 172.17.0.1 — gateway jaringan default bridge milik container sekali jalan ini, bukan 172.19.0.1 milik net-latihan. Dengan dua jalur ini, arsitektur “container bicara container dengan nama, container bicara host dengan host.docker.internal” dapat dipakai konsisten di semua platform.

Empat Alamat Khusus dalam Satu Tabel #

Seluruh alamat istimewa yang kita temui sepanjang section ini kini telah punya demo masing-masing:

AlamatMenunjuk keKetersediaan
127.0.0.11embedded DNS server daemonhanya di user-defined network
host.docker.internalmesin hostbawaan Docker Desktop; di Linux lewat --add-host dengan nilai host-gateway
172.19.0.1gateway network — wajah hostsetiap bridge network punya satu, mengikuti subnetnya
127.0.0.1loopback container sendiriselalu ada, di dalam namespace container

Baris terakhir menyimpan jebakan yang paling sering menjerat pendatang baru: 127.0.0.1 di dalam container bukanlah 127.0.0.1 milik host. Loopback adalah antarmuka milik network namespace — aplikasi di app yang menyambung ke localhost hanya dapat menjangkau proses lain di dalam container app itu sendiri, dan aplikasi di db yang mendengarkan 127.0.0.1 tidak pernah terjangkau oleh tetangganya. Konsekuensi praktisnya menyusul sebagai salah satu kasus utama chapter Troubleshooting Network.

Kita tutup studi kasus dengan membersihkan seluruh panggung:

bash
$ docker rm -f web db app
$ docker network rm net-latihan

Output yang Dihasilkan:

text
web
db
app
net-latihan

Percakapan container ke container kini tuntas dibuktikan: HTTP lewat nama, TCP lewat nama, dan host terjangkau lewat alamat istimewanya. Namun sepanjang chapter ini seluruh dunia luar tetap tidak bisa mengunjungi container web kita — dan memang itu niatnya. Pertanyaan berikutnya sudah menunggu di ambang pintu: bagaimana membuka pintu jaringan privat itu ke luar? Jawabannya adalah pemetaan port dengan flag -p, dan karena sintaks dasarnya sudah kita kuasai pada Chapter Port Publishing, chapter berikutnya memfokuskan diri pada dua hal yang jarang diajarkan: memverifikasi pemetaan secara ujung ke ujung, dan membaca resep diagnosis ketika pintu itu macet.

Referensi #