Tiga mode bawaan telah kita petakan: default bridge yang konektivitasnya lengkap tetapi bisu soal nama, mode host yang menanggalkan isolasi, dan mode none yang memutus segalanya. Kelemahan yang paling mengganjal tetap yang pertama — ping web-a yang ditolak dengan bad address pada Chapter Bridge Network — karena aplikasi nyata memang memanggil tetangganya dengan nama, bukan dengan angka IP yang bisa berpindah setiap kali container diciptakan ulang. Jawaban Docker atas kelemahan itu adalah user-defined bridge: jaringan buatan sendiri yang kelihatannya sama seperti default bridge, tetapi membawa DNS internal, kelompok isolasi sendiri, dan kendali penuh atas subnet. Chapter ini membangun jaringan pertama kita dari nol, menyambungkan container yang sedang berjalan tanpa mematikannya, lalu menyetel subnet dan IP statis untuk kebutuhan yang lebih spesifik.

Membuat Jaringan Pertama Kita #

Satu perintah cukup untuk melahirkan jaringan baru — beri nama yang menjelaskan fungsinya, karena nama inilah yang nanti dipakai setiap docker run:

bash
$ docker network create net-aplikasi

Output yang Dihasilkan:

text
d4c8b2a6e0f15a7c9e1b3d5f7a9c0e2b4d6f8a1c3e5b7d9f0a2c4e6b8d0f2a4c

Enam puluh empat karakter heksadesimal yang tercetak adalah ID lengkap jaringan baru kita, sama seperti ID container pada docker run. Kita periksa hasilnya di daftar jaringan:

bash
$ docker network ls

Output yang Dihasilkan:

text
NETWORK ID     NAME          DRIVER    SCOPE
9e7d2c4b1a8f   bridge        bridge    local
3f8a6d2c9e4b   host          host      local
7c1e9a3d5f2b   none          null      local
d4c8b2a6e0f1   net-aplikasi  bridge    local

Mari kita telaah baris baru tersebut:

  1. net-aplikasi dengan driver bridge — jaringan buatan kita tetap memakai driver bridge yang sama dengan default; yang berubah adalah statusnya sebagai jaringan yang kita definisikan sendiri, dan status itu membuka fitur yang tidak ada di default.
  2. ID pendek d4c8b2a6e0f1 — dua belas karakter pertama dari ID penuh yang barusan tercetak, sama hubungannya seperti CONTAINER ID dengan ID container.
  3. Subnet ditunjuk otomatis dari pool berikutnya — daemon tidak bertanya subnet apa yang kita mau; ia mengambil kumpulan alamat berikutnya setelah 172.17.0.0/16 milik docker0, yaitu 172.18.0.0/16, dan menyediakan 172.19.0.0/16 untuk jaringan keempat bila kelak kita buat lagi — urutan pool ini menjaga jaringan-jaringan di satu host tidak saling menabrak.

Container Pertama di net-aplikasi #

Jaringan baru kita menunggu penghuni. Kita jalankan pasangan klasik aplikasi dua tingkat — sebuah web server dan satu container utilitas yang mewakili aplikasi backend — keduanya secara eksplisit menempel ke net-aplikasi:

bash
$ docker run -d --name web --network net-aplikasi nginx

Output yang Dihasilkan:

text
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a
bash
$ docker run -d --name app --network net-aplikasi alpine:3.20 sleep 1h

Output yang Dihasilkan:

text
3b7e9f2a5c8d4617a9e0c2b4d6f8a1c3e5b7d9f0a2c4e6b8d0f2a4c6e8b1d3f5

Metadata lengkap jaringan kita kini berisi dua penghuni — inilah wujud docker network inspect untuk jaringan buatan sendiri:

bash
$ docker network inspect net-aplikasi

Output yang Dihasilkan:

json
[
    {
        "Name": "net-aplikasi",
        "Id": "d4c8b2a6e0f15a7c9e1b3d5f7a9c0e2b4d6f8a1c3e5b7d9f0a2c4e6b8d0f2a4c",
        "Created": "2026-10-13T19:21:48.392714853Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "3b7e9f2a5c8d4617a9e0c2b4d6f8a1c3e5b7d9f0a2c4e6b8d0f2a4c6e8b1d3f5": {
                "Name": "app",
                "EndpointID": "c9e2a7b4d1f68530a2c4e6b8d0f2a4c6e8b1d3f5a7c9e1b3d5f7a9c2e4b6d8f0",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a": {
                "Name": "web",
                "EndpointID": "a3f8c1e5b7d29406a8c2e4f0b6d8a1c3e5f7a9b1d3f5e7c9a1b3d5f7e9c2a4b6",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

Mari kita telaah tiga bagian yang paling berharga dari output di atas:

  1. IPAM.Config — subnet 172.18.0.0/16 dengan gateway 172.18.0.1 persis seperti ramalan pool berurutan: satu tingkat di atas 172.17.0.0/16 milik docker0; bendahara alamat jaringan ini membagi 172.18.0.2 kepada web dan 172.18.0.3 kepada app.
  2. Containers dengan MacAddress — MAC 02:42:ac:12:00:02 milik web memakai sandi alamat yang kita bedah pada Chapter Bridge Network: prefiks 02:42, lalu ac:12:00:02 yang terbaca sebagai 172.18.0.2; keajaiban kecil ini tetap bekerja identik di jaringan buatan sendiri.
  3. Scope: local dengan Options kosong — jaringan ini hidup pada satu host saja; kolom Options yang kosong menandakan kita menerima seluruh bawaan driver, berbeda dengan default bridge yang membawa baris-baris opsi com.docker.network.bridge karena jaringan itu dibangun oleh daemon dengan opsi yang diatur sejak awal.

Nama yang Akhirnya Bisa Dipanggil #

Momen yang gagal pada default bridge kini kita ulang di jaringan sendiri. Dari dalam app, kita sapa web — dengan namanya, tanpa satu angka IP:

bash
$ docker exec app ping -c 2 web

Output yang Dihasilkan:

text
PING web (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.149 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.112 ms

--- web ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.112/0.131/0.149 ms

Baris pertama mengatakan segalanya: PING web (172.18.0.2) — nama web berhasil diterjemahkan menjadi alamat, sesuatu yang mustahil terjadi di default bridge. Penerjemahnya adalah DNS internal Docker yang aktif otomatis pada user-defined network; bedah lengkap mesin ini — cara kerjanya, batasnya, dan perilakunya terhadap nama eksternal — menjadi materi utama Chapter Container DNS.

Menyambungkan Container yang Sedang Berjalan #

Default bridge memaksa kita memilih jaringan sebelum container menyala — salah pasang berarti buang dan jalankan ulang. User-defined network melepas kekangan itu: container yang sedang berjalan bisa kita colokkan ke jaringan tambahan tanpa mati sedetik pun. Kita siapkan satu reporter di jaringan bawaan:

bash
$ docker run -d --name reporter alpine:3.20 sleep 1h

Output yang Dihasilkan:

text
5a8c2e6b1d4f7a9c0e2b4d6f8a1c3e5b7d9f0a2c4e6b8d0f1a3c5e7b9d0f2a4c

Reporter kini menghuni default bridge dengan alamat 172.17.0.2. Sekarang kita tambahkan kartu jaringan keduanya — ke net-aplikasi:

bash
$ docker network connect net-aplikasi reporter

Perintah ini tidak mencetak apa pun saat berhasil — keheningan output di dunia Docker sering kali justru tanda keberhasilan, dan kita verifikasi hasilnya langsung dari dalam container:

bash
$ docker exec reporter ip addr

Output yang Dihasilkan:

text
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
27: eth0@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
29: eth1@if30: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 scope global eth1
       valid_lft forever preferred_lft forever

Container ini kini punya dua kartu jaringan: eth0 ke default bridge (172.17.0.2, perhatikan MAC dengan sandi ac:11) dan eth1 ke net-aplikasi (172.18.0.4, MAC bersandi ac:12). Pencabutannya sama halnya — satu perintah, tanpa restart:

bash
$ docker network disconnect net-aplikasi reporter

Sekali lagi tanpa output, dan docker exec reporter ip addr setelahnya akan menunjukkan eth1 sudah lenyap dari daftar — container kini tinggal satu kartu di default bridge, seolah tak pernah sempat punya kartu kedua.

Subnet Khusus dan IP Statis #

Pool otomatis 172.18, 172.19, dan seterusnya nyaman untuk umum, tetapi kadang kebijakan jaringan menuntut alamat tertentu — misalnya segmen kantor yang sudah dipesan untuk keluarga server internal. Docker membiarkan kita menuliskan subnet sendiri saat membuat jaringan:

bash
$ docker network create --subnet 10.10.0.0/24 net-kantor

Output yang Dihasilkan:

text
8b3e7c9a1d5f0a2c4e6b8d0f1a3c5e7b9d0f2a4c6e8a1b3d5f7a9c0e2b4d6f8a

Jaringan net-kantor kini berdiri di segmen 10.10.0.0/24 — dua ratus lima puluh empat alamat yang jauh lebih kecil dari /16, dan pilihan ukuran ini pun bagian dari desain. Di segmen inilah IP statis masuk akal: layanan yang seluruh tim tahu alamatnya, seperti printer kantor yang selama puluhan tahun jadi contoh klasik perangkat beralamat tetap:

bash
$ docker run -d --name printer --network net-kantor --ip 10.10.0.7 alpine:3.20 sleep 1h

Output yang Dihasilkan:

text
9f2b6d8a4c7e1b3d5f7a9c0e2a4c6e8b0d2f4a6c8e1a3b5d7f9a0c2e4b6d8f1a

Flag --ip 10.10.0.7 menuntut jaringannya memiliki subnet yang dideklarasikan — itulah sebabnya --subnet disertakan saat create. Kita verifikasi hasilnya dengan format ringkas:

bash
$ docker network inspect net-kantor --format '{{range .Containers}}{{.Name}} -> {{.IPv4Address}}{{end}}'

Output yang Dihasilkan:

text
printer -> 10.10.0.7/24

IP statis tampak memikat, tetapi dua peringatan menyertainya. Pertama, konflik alamat: daemon menolak memulai container bila alamat yang kita minta sudah diduduki penghuni lain — galat address already in use muncul dan container tidak jalan, sehingga setiap --ip harus dicatat dan dikoordinasikan seperti inventaris alamat kantor sungguhan. Kedua, IP statis mengunci topologi: container yang pindah host atau diciptakan ulang di jaringan berbeda akan kehilangan alamat tetapnya, dan konfigurasi yang bergantung pada angka itu ikut patah. Praktik yang lebih sehat tetap memakai nama dan DNS internal; IP statis disimpan untuk kasus khusus yang benar-benar menuntutnya.

Default Bridge versus User-Defined Bridge #

Seluruh perbedaan yang kita alami sepanjang section ini terangkum dalam satu tabel pembanding:

AspekDefault bridgeUser-defined bridge
Resolusi nama containerTidak ada — ping web-a gagal dengan bad addressDNS internal aktif — nama diterjemahkan otomatis
Mekanisme --linkSatu-satunya jalan, dan sudah ditinggalkanTidak dibutuhkan sama sekali
Kelompok isolasiSeluruh container tanpa --network bercampur satu jaringanTiap jaringan adalah dunia terpisah sendiri
Menyambungkan container berjalanTidak bisa — jaringan dipilih saat docker rundocker network connect dan disconnect kapan pun
Kendali subnet dan IPBawaan 172.17.0.0/16, tanpa IP statis--subnet, --gateway, --ip-range, hingga --ip per container

Sebelum menutup, kita bereskan sisa latihan sambil menjaga dua penghuni yang masih kita perlukan untuk chapter berikutnya:

bash
$ docker rm -f reporter printer
$ docker network rm net-kantor

Output yang Dihasilkan:

text
reporter
printer
net-kantor

Jaringan net-aplikasi sengaja kita biarkan hidup bersama penghuninya — web di 172.18.0.2 dan app di 172.18.0.3 — karena merekalah pemain utama chapter berikutnya. Nama web yang barusan berhasil dipanggil adalah pintu masuk pertama kita ke mesin di baliknya: DNS internal Docker yang bekerja pada alamat 127.0.0.11. Chapter Container DNS membedah mesin itu sampai ke roda giginya — bagaimana nama dicocokkan, apa yang terjadi pada nama eksternal seperti example.com, dan bagaimana alias memberi satu container banyak nama.

Referensi #