Jaringan Docker untuk Pemula: Bridge, DNS, Port, dan ufw
Jaringan Docker untuk pemula: bridge network, DNS antar container, publish port, kenapa ufw tidak memblokirnya, dan arti localhost di dalam container.
Oleh Matthews WongDitinjau pada
Poin utama
Jaringan Docker adalah cara container saling menjangkau dan menjangkau dunia di luar host. Setiap container punya network stack sendiri, termasuk loopback-nya. Di user-defined network, yang dibuat Compose untuk setiap project, container saling menemukan lewat nama berkat DNS bawaan Docker. Publish meneruskan port host ke port container, di semua alamat host kecuali Anda mengikatnya ke 127.0.0.1.
Setelah pelajaran ini, Anda bisa
- Menjelaskan kenapa container di user-defined network bisa saling menemukan lewat nama, dan port mana yang dipakai untuk terhubung.
- Mem-publish port hanya di loopback, dan menjelaskan kenapa port yang di-publish bisa lolos dari ufw.
- Membedakan localhost milik container dari localhost milik host, dan memperbaiki aplikasi yang hanya listen di loopback-nya sendiri.
Ikuti diagramnya langkah demi langkah
Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.
Bagaimana request menemukan container
Aplikasi Compose dengan dua service di satu host: container saling menemukan lewat nama berkat DNS bawaan Docker, port yang di-publish diteruskan melewati ufw kecuali diikat ke loopback, dan aplikasi harus listen di luar loopback container-nya sendiri agar bisa menjawab.
Tombol: ← → langkah · Home / End · K putar atau jeda
Satu host Docker. Compose menaruh web dan db di user-defined bridge network yang punya DNS sendiri. Di luarnya ada ufw, nginx, port yang di-publish, dan internet.
web menanyakan db ke DNS bawaan Docker, lalu terhubung ke db:5432, yaitu port container. Tidak ada yang di-publish untuk ini: container dalam satu network langsung saling menjangkau port-nya.
postgres://db:5432Tebak langkah berikutnya ufw menolak port 3002, dan web di-publish dengan -p 3002:3000. Orang asing terhubung ke 3002. Apa yang terjadi? (ufw membuang paketnya · Paketnya tetap sampai ke container)-p 3002:3000 listen di semua alamat host. Aturan NAT Docker membelokkan paket sebelum sampai ke chain yang dipakai ufw, jadi aturan deny tidak pernah melihatnya.
ports: - "3002:3000"127.0.0.1:3002:3000 hanya listen di loopback host, jadi host lain tidak bisa menjangkaunya. nginx berjalan di host yang sama, jadi bisa, dan menjadi satu-satunya pintu masuk.
- "127.0.0.1:3002:3000"Tebak langkah berikutnya Di dalam web, aplikasi listen di 127.0.0.1:3000. Apakah request nginx lewat port yang di-publish mendapat jawaban? (Ya, 127.0.0.1 tetap 127.0.0.1 · Tidak, tidak ada yang menjawab)Setiap container punya loopback sendiri. Docker meneruskan port yang di-publish ke alamat network container, jadi aplikasi yang hanya listen di 127.0.0.1 milik container tidak pernah menjawab.
server.listen(3000, "127.0.0.1")Image situs ini menyetel HOSTNAME=0.0.0.0, jadi Next.js listen di semua alamat container. Request yang diteruskan dijawab, dan 127.0.0.1:3000 milik health check tetap berfungsi di dalam.
ENV HOSTNAME=0.0.0.0
Apa itu jaringan Docker?
Jaringan Docker adalah sekumpulan network virtual yang menghubungkan container satu sama lain dan dengan dunia di luar host. Setiap container berjalan di network namespace-nya sendiri, yang dipakai Linux untuk memisahkan network device, protocol stack, routing table, aturan firewall, dan nomor port. Jadi container punya interface sendiri, loopback sendiri, dan port sendiri. Dokumentasi Docker menjelaskannya dari sisi container: yang terlihat hanya sebuah network interface dengan alamat IP, gateway, dan routing table.
Baca selengkapnya (1)Tutup
Network yang diikuti sebuah container menentukan apa yang bisa dijangkaunya. Jalankan docker network ls di instalasi baru dan Anda akan melihat tiga: bridge, host, dan none. bridge adalah driver default. host menghilangkan isolasi antara container dan host. none hanya menyisakan loopback device untuk container. Overlay network, yang menghubungkan beberapa host Docker untuk Swarm, adalah topik tersendiri.
Bagaimana container saling menemukan lewat nama?
Tidak di default bridge. Container yang dijalankan tanpa --network masuk ke network bernama bridge, tempat container hanya bisa saling menjangkau lewat alamat IP, kecuali Anda memakai opsi lama --link. Tutorial Docker sendiri menunjukkan hasilnya: ping lewat nama gagal dengan pesan bad address. Tutorial yang sama menyebut default bridge sebagai detail warisan Docker yang tidak disarankan untuk production.
Baca selengkapnya (2)Tutup
Di user-defined network, jenis yang dibuat oleh docker network create, Docker menjalankan DNS server bawaan. Container saling me-resolve lewat nama atau alias, dan lookup untuk nama di luar diteruskan ke DNS server yang dikonfigurasi di host. Karena itu connection string antar container sebaiknya memakai nama, bukan alamat.
Compose melakukan ini untuk Anda. Untuk setiap project, Compose membuat network yang dinamai sesuai nama project, yang secara default adalah nama direktorinya, ditambah _default, dan network itu memakai driver bridge. Setiap service bergabung ke sana dan mendaftarkan namanya, jadi service bernama db bisa dijangkau di hostname db dari service lain mana pun dalam file itu.
Port mana yang dipakai antar container?
Port container. Panduan networking Compose menyatakannya dengan jelas: komunikasi antar service memakai port container, dan port host hanya dipakai dari luar network. Kalau db di-publish sebagai 5433:5432, service lain tetap terhubung ke db:5432. Container dalam user-defined network yang sama praktis membuka semua port-nya satu sama lain, tanpa perlu publish sama sekali.
Baca selengkapnya (1)Tutup
Network juga cara memisahkan sesuatu. Container di bridge network yang berbeda hanya bisa saling menjangkau lewat port yang di-publish. Di Compose, key networks di level atas memungkinkan proxy berbagi satu network dengan aplikasi, sementara aplikasi berbagi network lain dengan database, sehingga proxy sama sekali tidak punya jalur ke database.
Apa yang sebenarnya terjadi saat port di-publish?
Publish, lewat -p HOST_PORT:CONTAINER_PORT atau entri ports di Compose, membuat Docker menambahkan aturan firewall yang menerjemahkan trafik dari port host ke port container. Tanpa alamat host, Docker mem-publish di semua alamat host, 0.0.0.0 dan [::], dan dokumentasinya menyebut ini tidak aman secara default: port terbuka untuk dunia luar, bukan hanya untuk host. Taruh 127.0.0.1 di depannya, maka hanya host Docker yang bisa menjangkaunya. Pengaturan ip di daemon bisa menjadikan loopback sebagai default.
Baca selengkapnya (1)Tutup
EXPOSE 3000, di bagian akhir Dockerfile situs ini, tidak mem-publish apa pun. Referensi Docker menyebutnya dokumentasi antara orang yang mem-build image dan orang yang menjalankan container. Hanya docker run -P yang memakainya, dengan mem-publish setiap port yang di-expose ke port bernomor tinggi di host. Pelajaran 12 membahas EXPOSE dari sisi hardening; di sini intinya hanya ke mana trafik mengalir.
Kenapa ufw tidak memblokir port yang di-publish?
Karena paketnya tidak pernah sampai ke aturan ufw. Dokumentasi Docker menjelaskannya: Docker merutekan trafik container di tabel nat, jadi paket dibelokkan sebelum sampai ke chain INPUT dan OUTPUT yang dipakai ufw, sehingga konfigurasi firewall Anda praktis diabaikan. Referensi Compose menyebut hal yang sama untuk port tanpa alamat host: port itu terikat ke semua interface dan melewati aturan firewall host. Aturan ufw yang menolak 3002 tidak menghentikan trafik ke container yang di-publish di 3002.
Baca selengkapnya (1)Tutup
Jangan memperbaikinya dengan mengedit aturan milik Docker; dokumentasi Docker menyatakan aturan itu tidak boleh Anda ubah. Ikat setiap port yang hanya dibutuhkan proxy lokal ke 127.0.0.1, supaya tidak ada yang terbuka ke publik untuk difilter, dan biarkan proxy di host, yang dijaga ufw, menjadi satu-satunya pintu masuk. Itulah yang dilakukan file compose situs ini.
Apakah localhost di dalam container itu host?
Bukan. Di dalam container, localhost dan 127.0.0.1 adalah loopback milik container itu sendiri, karena container punya network namespace sendiri. Driver none memperlihatkannya: container di sana hanya mendapat loopback device, tidak ada yang lain. Jadi connection string bertuliskan localhost:5432 di dalam container aplikasi menunjuk ke container aplikasi itu sendiri, bukan ke database di host atau di container lain.
Baca selengkapnya (2)Tutup
Berlaku juga sebaliknya. Port yang di-publish diteruskan ke alamat network container, bukan ke loopback-nya, jadi aplikasi yang hanya listen di 127.0.0.1 di dalam container tidak pernah menjawab. Dalam uji coba di Docker Engine 29.8.1, curl ke port yang di-publish mendapat empty reply sampai aplikasinya listen di 0.0.0.0. Karena itu Dockerfile situs ini menyetel HOSTNAME=0.0.0.0, yang dibaca server standalone Next.js sebagai alamat untuk listen.
Untuk menjangkau service di host dari dalam container, Docker Desktop sudah me-resolve nama host.docker.internal untuk Anda; di tempat lain, tambahkan --add-host host.docker.internal=host-gateway, yang mengarah ke alamat internal host. Driver host network adalah opsi yang kasar: container berbagi network namespace dengan host, tidak punya alamat sendiri, dan Docker membuang setiap -p disertai peringatan. Driver ini jalan di Docker Engine di Linux, dan di Docker Desktop 4.34 ke atas setelah Anda mengaktifkannya di Settings.
Konfigurasi nyata di balik pelajaran ini
Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.
ports:
# Bind to loopback only — nginx terminates TLS and proxies to this port.
- "127.0.0.1:${APP_PORT:-3002}:3000"Tiga bagian, baca dari kanan ke kiri: port container 3000, port host APP_PORT (3002 jika kosong), dan alamat host 127.0.0.1. Karena alamat host-nya adalah loopback milik host, host lain tidak bisa menjangkau port itu dan tidak ada yang bisa terlewat oleh ufw. nginx, di server yang sama, terhubung ke port itu dan menjadi satu-satunya pintu masuk.
ENV PORT=3000
ENV HOSTNAME=0.0.0.0Server standalone Next.js membaca PORT dan HOSTNAME sebelum start. 0.0.0.0 ini berada di dalam network namespace milik container: artinya semua alamat container, jadi trafik yang diteruskan mendapat jawaban. Alamat host mana yang terbuka diputuskan terpisah, oleh entri ports di atas.
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://127.0.0.1:3000/robots.txt",
]Docker menjalankan perintah ini di dalam container, jadi 127.0.0.1:3000 adalah loopback milik container dan port asli aplikasi, bukan port 3002 milik host. Ini berfungsi karena aplikasi listen di 0.0.0.0, yang mencakup loopback container.
Kesalahan yang benar-benar membuang waktu
ufw menolak port itu, tetapi internet tetap bisa masuk
Entri ports tanpa alamat host listen di semua alamat host, dan aturan NAT Docker merutekan trafiknya sebelum chain ufw berjalan. Aturan deny-nya ada; hanya saja tidak pernah melihat paketnya. Ikat ke 127.0.0.1, atau ke satu interface yang memang harus listen, lalu pastikan dengan docker port.
Empty reply dari port yang di-publish
Port sudah di-publish, container berjalan, dan docker exec dengan wget ke 127.0.0.1 di dalamnya berhasil, tetapi curl dari host gagal. Aplikasinya hanya listen di loopback container, sedangkan trafik yang diteruskan tiba di alamat network container. Buat aplikasi listen di 0.0.0.0: HOSTNAME untuk Next.js, atau pengaturan host milik server Anda.
bad address: nama yang tidak ter-resolve
Container yang dijalankan dengan docker run biasa berada di default bridge, yang tidak punya resolusi nama antar container. Buat sebuah network, jalankan kedua container di sana, dan nama yang sama langsung berfungsi. Di Compose, service dalam network project langsung bisa saling me-resolve lewat nama service.
localhost atau port host di connection string
postgres://localhost:5432 di dalam container aplikasi menunjuk ke container aplikasi itu sendiri. postgres://db:5433, yang memakai port host hasil publish, juga meleset. Antar container, pakai nama service dan port container: db:5432.
| Network | Menemukan container lewat nama? | Port | Menurut Docker |
|---|---|---|---|
| Default bridge | Tidak, hanya alamat IP (atau --link yang sudah lama) | -p mem-publish ke host | Detail warisan, tidak disarankan untuk production |
| User-defined bridge (default Compose) | Ya, lewat nama atau alias, melalui DNS bawaan | Semua port terbuka antar anggota; -p untuk host | Lebih baik daripada default bridge |
| host | Tidak punya network sendiri: berbagi milik host | -p dibuang; aplikasi listen langsung di host | Menghilangkan isolasi antara container dan host |
| none | Tidak, hanya ada loopback device | Tidak ada yang bisa di-publish | Mengisolasi container sepenuhnya |
Lakukan di komputer Anda sendiri
Anda hanya butuh Docker di laptop; semua port di bawah tetap di 127.0.0.1. Setiap langkah menunjukkan satu aturan dari pelajaran ini, dan langkah terakhir membersihkan semuanya.
Lihat network yang ada di instalasi baru: bridge, host, dan none.
docker network lsDi default bridge, nama tidak ter-resolve. Hasilnya ping: bad address 'demo-a'.
docker run -d --name demo-a alpine:3 sleep 600 docker run --rm alpine:3 ping -c 1 demo-aDi network yang Anda buat, lookup yang sama berhasil: wget mencetak halaman sambutan nginx. Dari default bridge, nama itu tetap gagal.
docker network create demo-net docker run -d --name demo-web --network demo-net nginx:alpine docker run --rm --network demo-net alpine:3 wget -qO- http://demo-web docker run --rm alpine:3 wget -qO- -T 3 http://demo-webPublish dengan dan tanpa alamat host, lalu bandingkan hasil docker port: 0.0.0.0 dan [::] untuk yang pertama, hanya 127.0.0.1 untuk yang kedua.
docker run -d --name demo-all -p 8081:80 nginx:alpine docker port demo-all docker run -d --name demo-lo -p 127.0.0.1:8082:80 nginx:alpine docker port demo-lo curl -I http://127.0.0.1:8082Jalankan server kecil yang listen di 127.0.0.1 milik container, dan beri waktu sedetik untuk start. curl lewat port yang di-publish gagal dengan balasan kosong atau reset, sedangkan request yang sama dari dalam container berhasil.
docker run -d --name demo-inside -e HOST=127.0.0.1 -p 127.0.0.1:8083:3000 node:22-alpine \ node -e "require('http').createServer((req, res) => res.end('hello\n')).listen(3000, process.env.HOST)" curl http://127.0.0.1:8083 docker exec demo-inside wget -qO- http://127.0.0.1:3000Jalankan lagi dengan listen di 0.0.0.0, perbaikan yang dilakukan Dockerfile situs ini lewat HOSTNAME. Beri waktu sedetik untuk start; sekarang curl mencetak hello.
docker rm -f demo-inside docker run -d --name demo-inside -e HOST=0.0.0.0 -p 127.0.0.1:8083:3000 node:22-alpine \ node -e "require('http').createServer((req, res) => res.end('hello\n')).listen(3000, process.env.HOST)" curl http://127.0.0.1:8083Bersihkan container dan network-nya.
docker rm -f demo-a demo-web demo-all demo-lo demo-inside docker network rm demo-net
Tiga pertanyaan sebelum lanjut
Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada nilai, jadi tebak saja dengan bebas; jawaban yang salah disimpan di browser ini agar kursus bisa menanyakannya lagi nanti.
Dua pertanyaan dari pelajaran sebelumnya
Mengingat kembali pelajaran lama setelah jeda membuatnya lebih melekat. Jawab dari ingatan sebelum mencari apa pun.
Jawab dalam hati, lalu cek
1.Kenapa dua container yang dijalankan dengan docker run biasa tidak bisa saling menjangkau lewat nama?Lihat jawabannya
Jawaban:Keduanya berada di default bridge, tempat container hanya bisa memakai alamat IP. Hanya user-defined network yang mendapat DNS bawaan Docker, dan Compose membuatnya untuk setiap project, jadi service-nya bisa saling me-resolve lewat nama.
2.File compose situs ini punya 127.0.0.1 di entri ports dan di health check. Apakah keduanya berarti mesin yang sama?Lihat jawabannya
Jawaban:Tidak. Di entri ports itu loopback milik host, jadi hanya server itu yang bisa menjangkau port 3002. Health check berjalan di dalam container, jadi 127.0.0.1:3000 miliknya adalah loopback container dan port asli aplikasi.
3.Apa pengaruh EXPOSE 3000 di Dockerfile terhadap network?Lihat jawabannya
Jawaban:Tidak ada, kalau berdiri sendiri. EXPOSE mendokumentasikan port yang diharapkan di-publish oleh image itu. Publish terjadi lewat -p, entri ports, atau -P, yang mem-publish setiap port yang di-expose ke port host bernomor tinggi.
Bahasan mendalam terkait di situs ini
Sumber
Sumber primer yang diperiksa pada tanggal riset. Ikuti tautannya untuk versi terbaru.
- Dokumentasi Docker: ikhtisar networking (driver, layanan DNS)
- Dokumentasi Docker: driver bridge network
- Dokumentasi Docker: networking untuk container standalone
- Dokumentasi Docker: networking di Compose
- Dokumentasi Docker: publish dan mapping port
- Dokumentasi Docker: packet filtering dan firewall (ufw)
- Dokumentasi Docker: referensi service Compose (ports)
- Dokumentasi Docker: referensi Dockerfile, EXPOSE
- Dokumentasi Docker: driver host network
- Dokumentasi Docker: driver none network
- Dokumentasi Docker: docker container run (--add-host host-gateway, -p)
- Dokumentasi Docker: panduan networking Docker Desktop (host.docker.internal)
- Dokumentasi Next.js: output standalone (PORT dan HOSTNAME)
- man page: network_namespaces(7)