Tiga pertanyaan menentukan hidup sebuah container di jaringan: bagaimana container mencapai internet, bagaimana container saling menemukan, dan bagaimana dunia luar masuk ke dalamnya. Ketiganya dijawab oleh kombinasi perangkat kernel — bridge, veth, netfilter, dan DNS — yang dikendalikan engine container. Chapter ini membedah lapisan-lapisan itu dari bawah ke atas: driver jaringan, mekanisme NAT di host, format port publishing, hingga resolusi nama antar container. Tanpa docker di lingkungan latihan, kita memakai fixture aturan NAT dan JSON hasil docker network inspect yang dibedah dengan jq.

Empat Driver Jaringan #

Setiap container menempel pada satu network, dan setiap network punya driver — keputusan arsitektural tentang seberapa jauh container diisolasi dari jaringan host:

DriverCara kerjaKapan dipakai
bridgecontainer dapat IP privat (172.17.x.x), keluar lewat NATdefault; aplikasi biasa
hostcontainer memakai stack jaringan host langsung, tanpa isolasibutuh performa jaringan maksimum
nonehanya ada loopback lo di dalam containerjob batch tanpa jaringan
overlayjaringan virtual yang merentang lintas hostcluster multi-mesin

Driver bridge adalah dunia yang kita tempati sepanjang section ini — aplikasi terisolasi rapi tetapi tetap bisa keluar-masuk lewat terjemahan alamat di host. Driver host menghapus isolasi itu sepenuhnya: container mendengarkan port di host seolah proses biasa, jadi dua container tidak bisa sama-sama memakai port 80. Driver overlay baru relevan saat satu aplikasi tersebar di banyak mesin, topik di luar cakupan chapter ini.

Bridge dan NAT di Balik Layar #

Saat engine container pertama kali jalan, ia membuat docker0 — bridge di host dengan subnet 172.17.0.0/16. Setiap container yang menyala mendapat sepasang perangkat virtual: satu ujung veth menempel di bridge, ujung lainnya menjadi eth0 di dalam container. Sketsa lengkap alurnya:

text
        klien 203.0.113.5
              |
              |  TCP ke 192.0.2.10 port 8080
              v
+--------------- host (laptop-budi) ---------------+
|  eth0 192.0.2.10                                 |
|    |                                             |
|    |  rule DNAT: 8080 -> 172.17.0.2:80           |
|    v                                             |
|  docker0 172.17.0.1/16 (bridge)                  |
|    |             |                               |
|   veth           veth                            |
|    |             |                                |
|   web :80       db :5432                          |
|  172.17.0.2     172.17.0.3                        |
+---------------------------------------------------+

Dua arah lalu lintas melewati host dengan dua perlakuan berbeda. Arah keluar (container ke internet): paket bersumber 172.17.0.2 tidak akan pernah dijawab oleh internet, karena alamat itu bersifat privat, jadi rule MASQUERADE menulis ulang sumbernya menjadi IP host. Arah masuk (internet ke container): rule DNAT mengubah tujuan paket dari host port 8080 menjadi 172.17.0.2 port 80. Berkas nat-rules di bundle materi memuat keduanya dalam format iptables:

bash
cat nat-rules
echo '== terjemahan port host yang masuk =='
cari() {
  hasil=$(grep -- "--dport $1 " nat-rules | sed 's/.*--to-destination //')
  if [ -n "$hasil" ]; then
    echo "port host $1 -> $hasil"
  else
    echo "port host $1 -> tidak ada rule, koneksi ditolak"
  fi
}
cari 8080
cari 5432

Output yang Dihasilkan:

text
# aturan gaya iptables yang dipasang engine container di table nat
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -d 172.17.0.2 --dport 80 -j ACCEPT
-A DOCKER ! -i docker0 -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
== terjemahan port host yang masuk ==
port host 8080 -> 172.17.0.2:80
port host 5432 -> tidak ada rule, koneksi ditolak

Mari kita bedah baris demi baris:

  1. Baris MASQUERADE: seluruh paket bersumber subnet 172.17.0.0/16 yang bukan menuju container lain (! -o docker0) keluar dengan alamat host — inilah kenapa container bisa memanggil server publik.
  2. Baris ACCEPT: meloloskan paket balasan koneksi yang sudah tercatat — ditangani connection tracking kernel, bukan ditulis manual.
  3. Baris DNAT: satu-satunya pintu masuk — paket TCP ke port 8080 di host dialihkan ke 172.17.0.2 port 80.
  4. Fungsi cari(): membaca tabel rule dengan grep; spasi di akhir pola "--dport $1 " mencegah port 8080 ikut cocok dengan 80801.

Balasan menemukan jalan pulang lewat connection tracking: kernel mencatat setiap terjemahan yang pernah dilakukan, jadi paket balasan dari 172.17.0.2 otomatis ditulis balik menjadi 192.0.2.10:8080 sebelum dikirim ke klien. Port 5432 tidak punya rule DNAT — koneksi dari luar ditolak, persis seperti hasil cari 5432.

Membaca Format -p #

Port publishing ditulis dengan opsi -p dan format [hostIP:]hostPort:containerPort — bertambah ke kiri bila butuh kontrol lebih. Kita bedah dua bentuknya dengan fungsi parser kecil:

bash
bedah() {
  spec=$1
  kontainer=${spec##*:}
  sisa=${spec%:*}
  if [ "$sisa" = "$spec" ]; then
    hostip=0.0.0.0
    hostport=$spec
  else
    case $sisa in
      *:*) hostip=${sisa%%:*}; hostport=${sisa##*:} ;;
      *)   hostip=0.0.0.0;   hostport=$sisa ;;
    esac
  fi
  printf '%s -> dengar di %s:%s, teruskan ke container port %s\n' \
    "$spec" "$hostip" "$hostport" "$kontainer"
}
bedah 8080:80
bedah 127.0.0.1:5432:5432

Output yang Dihasilkan:

text
8080:80 -> dengar di 0.0.0.0:8080, teruskan ke container port 80
127.0.0.1:5432:5432 -> dengar di 127.0.0.1:5432, teruskan ke container port 5432

Tiga operasi string membongkar spesifikasinya:

  1. ${spec##*:}: membuang segalanya sebelum titik dua terakhir — port container, bagian yang selalu wajib ada.
  2. ${spec%:*}: menyisakan bagian depan; bila hasilnya sama dengan masukan, berarti tidak ada titik dua sama sekali.
  3. case $sisa in *:*): bagian depan yang masih mengandung titik dua berarti ada IP host di dalamnya — %%:* mengambil IP, ##*: mengambil port host.
  4. IP kosong berarti 0.0.0.0: mendengarkan di semua interface host.

DNS Antar Container #

Alamat 172.17.0.2 tidak boleh dikodekan di aplikasi — IP container berubah setiap start. Lapisan berikutnya menyelesaikannya dengan nama: pada user-defined network, engine container menjalankan DNS internal di 127.0.0.11, dan nama service (di compose) atau --name (di docker run) menjadi hostname yang bisa di-resolve. Aplikasi cukup menyambung ke db — bukan 172.20.0.3. Berkas toko-net.json di bundle materi adalah hasil docker network inspect toko-net; kita bacanya dengan jq:

bash
jq -r '.[0].Name, .[0].Driver, .[0].IPAM.Config[0].Subnet' toko-net.json
echo '== container yang tergabung =='
jq -r '.[0].Containers | to_entries[] | "\(.value.Name) -> \(.value.IPv4Address)"' toko-net.json | sort
echo '== resolusi nama db =='
jq -r '.[0].Containers | to_entries[] | select(.value.Name == "db") | .value.IPv4Address' toko-net.json

Output yang Dihasilkan:

text
toko-net
bridge
172.20.0.0/16
== container yang tergabung ==
db -> 172.20.0.3/16
web -> 172.20.0.2/16
== resolusi nama db ==
172.20.0.3/16

Tiga kueri itu bertingkat:

  1. .Name, .Driver, .IPAM.Config[0].Subnet: identitas jaringan — nama, driver, dan subnet yang dialokasikan (172.20.0.0/16, satu blok privat per network).
  2. Containers | to_entries[]: field Containers adalah objek yang key-nya ID container; to_entries mengubahnya menjadi daftar pasangan agar bisa diiterasi dan diformat ulang.
  3. select(.value.Name == "db"): menyaring entri bernama db — persis query yang dilayani DNS internal ketika aplikasi menanyakan nama db.

Jaringan menjawab pertanyaan pergerakan — data bergerak masuk, keluar, dan menyamping. Pertanyaan penutupnya tentang waktu: ke mana data pergi ketika containernya dihapus? Jawabannya ada di lapisan penyimpanan — volume, bind mount, dan tmpfs — topik Chapter Container Storage .

Referensi lanjut: dokumentasi jaringan Docker untuk pembahasan tiap driver beserta topology-nya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

toko-net.json

Unduh berkas
json
[
  {
    "Name": "toko-net",
    "Id": "b7a1f9e4c2d8",
    "Created": "2026-01-15T09:30:00Z",
    "Driver": "bridge",
    "IPAM": {
      "Config": [
        {
          "Subnet": "172.20.0.0/16",
          "Gateway": "172.20.0.1"
        }
      ]
    },
    "Containers": {
      "2f1b7c9d3e5a": {
        "Name": "db",
        "IPv4Address": "172.20.0.3/16"
      },
      "8e4a1d6c7b2f": {
        "Name": "web",
        "IPv4Address": "172.20.0.2/16"
      }
    }
  }
]