Networking Docker Compose: DNS, Host, dan Port
Pahami network bridge default, DNS berdasarkan nama service, kapan host mode layak digunakan, berbagi network antarproject, serta port publik yang melewati UFW.
Hal yang dibangun Compose sebelum aplikasi Anda dimulai
Networking Docker Compose dimulai dengan satu aturan: docker compose up membuat network privat untuk project, menghubungkan setiap service ke network tersebut, dan memungkinkan service-service itu saling menjangkau berdasarkan nama service. Anda tidak perlu menulis satu baris networks: pun untuk mendapatkannya. Sebagian besar kebingungan tentang networking Compose terjadi karena default tersebut sudah tersedia.
Berikut file sederhana. Simpan sebagai compose.yaml di direktori bernama shop.
services:
web:
image: nginx:1.27
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: exampleJalankan dan lihat yang dibuat Docker:
docker compose up -d
docker network lsDaftar tersebut sekarang berisi network bernama shop_default. Compose menamainya <project>_default, dan nama project secara default menggunakan nama direktori dalam huruf kecil. Ganti dengan docker compose -p myproject up -d atau dengan name: myproject tingkat teratas di dalam file. Driver-nya adalah bridge, yaitu switch virtual di dalam host. Setiap container mendapat alamat pada subnet privat, dan traffic keluar diterjemahkan ke alamat host saat keluar.
docker compose down menghapus network tersebut. Karena itu, container lama dari project sebelumnya dapat membuat network tetap terbuka: Docker menolak operasi tersebut dengan error while removing network: network shop_default has active endpoints, dan solusinya adalah menghentikan atau menghapus container yang masih terhubung ke network itu.
Jika Compose masih baru bagi Anda, tata letak file Compose dan perintah siklus hidup layak dibaca terlebih dahulu, karena semua bagian di bawah ini mengasumsikan Anda dapat memulai dan menghentikan project.
Resolusi DNS berdasarkan nama service adalah bagian yang sering terlewat oleh pemula
Pada jaringan yang dibuat pengguna, Docker menjalankan server DNS tertanam yang dilihat setiap container pada 127.0.0.11. Server ini meresolusikan nama service ke alamat container saat ini. Jadi, web dapat mengakses database pada hostname db dan port 5432 tanpa konfigurasi apa pun.
docker compose exec web getent hosts dbPerintah tersebut mencetak baris seperti 172.18.0.2 db. Jika tidak mencetak apa pun, kedua service tidak berada pada jaringan yang sama.
Kesalahan yang hampir semua orang lakukan setidaknya sekali adalah menggunakan localhost dalam konfigurasi aplikasi. Di dalam container, localhost adalah container tersebut, bukan host dan bukan service lain. Klien Postgres menampilkannya dengan jelas:
could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP connections on port 5432?String koneksinya harus berupa postgresql://postgres:example@db:5432/postgres. Bagian host adalah nama service.
Ada dua detail yang akan menghemat waktu nantinya. Nama diresolusikan ke apa pun yang sedang berjalan saat ini. Jadi, docker compose up -d --scale web=3 menghasilkan satu nama dengan tiga alamat. Klien yang menyimpan cache DNS selamanya akan terikat pada container yang sudah tidak aktif. Selain itu, jaringan lama bridge yang digunakan oleh docker run biasa tanpa --network sama sekali tidak dapat meresolusikan nama container. Container di jaringan tersebut hanya dapat menemukan satu sama lain melalui alamat IP. Untuk hostname di luar host, container menggunakan salinan /etc/resolv.conf milik host. Karena itu, saran tentang container link dari 2016 tidak sesuai dengan kondisi yang Anda lihat. Karena container mewarisi resolver milik host, server yang diarahkan ke AdGuard DNS juga meneruskan kueri container ke sana, dan alamat AdGuard DNS yang Anda pilih menentukan apakah domain iklan dan pelacak ikut diblokir untuk aplikasi di dalam container.
Anda tidak memerlukan ports: untuk menghubungkan dua layanan
ports: memublikasikan port container pada host. Fitur ini digunakan untuk lalu lintas yang datang dari luar Docker. Fitur ini tidak terkait dengan lalu lintas antarlayanan, yang sudah berfungsi pada seluruh rentang port di jaringan project.
Karena itu, ports: - "5432:5432" yang sering ditambahkan ke layanan database tidak memberikan manfaat dan justru menimbulkan risiko nyata: konfigurasi tersebut mengekspos Postgres pada antarmuka publik server. Hapus konfigurasi itu. Jika Anda ingin mengaksesnya dari laptop untuk melakukan migrasi, ikat ke loopback dengan "127.0.0.1:5432:5432" dan akses melalui tunnel SSH. Perbedaan antara listening socket, port yang dipublikasikan, dan aturan firewall dibahas dalam cara kerja port dan layanan yang melakukan listening di Linux.
expose: hanya berfungsi sebagai dokumentasi dalam Compose. Konfigurasi ini tidak membuka apa pun, karena tidak ada akses yang ditutup antarkontainer pada jaringan yang sama.
Kapan network_mode host tepat digunakan dan apa konsekuensinya
Host mode menghapus network namespace milik container dan memungkinkan proses menggunakan interface host secara langsung.
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinityAda alasan yang valid untuk menggunakannya. Proses yang perlu melihat trafik broadcast atau multicast pada jaringan lokal, seperti penemuan perangkat untuk media server atau hub otomatisasi rumah, tidak dapat melihatnya dari balik bridge. Bridge tidak meneruskan trafik tersebut ke container. Agen pemantauan yang membaca penghitung interface host memerlukan interface host. Anda juga melewati tahap penerjemahan alamat, yang penting pada laju paket tinggi.
Konsekuensinya bersifat spesifik.
ports: tidak lagi berfungsi. Docker memperingatkan bahwa port yang dipublikasikan akan diabaikan saat menggunakan host network mode. Container akan melakukan binding pada apa pun yang digunakan oleh prosesnya. Dua container host mode yang ingin menggunakan port 8080 akan mengalami konflik. Container kedua berhenti dengan bind: address already in use.
Resolusi nama berdasarkan nama service tidak tersedia lagi di kedua arah. Container tidak berada pada jaringan project, sehingga tidak dapat meresolusikan db. Service lain juga tidak dapat meresolusikan container tersebut. Container masih dapat mengakses service lain melalui port yang dipublikasikan pada host, biasanya melalui 127.0.0.1. Di Linux, container juga dapat mengakses container bridge secara langsung melalui alamat IP container tersebut pada port mana pun yang sedang listening. Host memiliki akses terbuka ke container pada jaringan bridge-nya, dan container host mode berbagi network stack milik host. Alamat tersebut berubah saat container dibuat ulang. Karena itu, port yang dipublikasikan pada loopback adalah rute yang stabil.
Isolasi tidak lagi tersedia. Proses yang melakukan binding pada 0.0.0.0 di dalam container host mode akan listening pada setiap interface server Anda, termasuk interface publik, sama seperti package yang diinstal dengan apt. Ada satu keuntungan dari kondisi ini. Trafik tersebut mengikuti jalur input normal, sehingga aturan UFW berlaku untuknya. Hal ini tidak berlaku pada port yang dipublikasikan.
Host mode adalah fitur Linux Docker Engine. Docker Desktop hanya mendukungnya mulai versi 4.34 dan setelah Anda mengaktifkannya. Selain itu, container tidak dapat melakukan binding pada alamat IP host, dan hanya TCP serta UDP yang ditangani. Jika sebagian tim menggunakan server Linux dan sebagian lainnya menggunakan Docker Desktop, file yang sama dapat berperilaku berbeda.
Gunakan host mode jika Anda perlu mengakses interface host. Jangan menggunakannya untuk memperbaiki masalah koneksi, karena biasanya Anda hanya mengganti satu masalah dengan masalah yang lebih sulit.
Menghubungkan dua project Compose dengan network eksternal
Network yang dibuat oleh satu project tidak terlihat oleh project lain. Karena itu, reverse proxy di proxy/compose.yaml tidak dapat melihat aplikasi di app/compose.yaml, meskipun berada di server yang sama. Solusinya adalah network yang tidak dimiliki oleh kedua project tersebut.
Buat network tersebut satu kali secara manual:
docker network create edgeKemudian deklarasikan sebagai eksternal di setiap project. Sisi proxy:
services:
proxy:
image: traefik:v3.1
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- edge
networks:
edge:
name: edge
external: trueSisi aplikasi:
services:
app:
image: nginx:1.27
networks:
- edge
- internal
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
networks:
- internal
networks:
edge:
name: edge
external: true
internal:external: true memberi tahu Compose untuk terhubung ke network yang sudah ada, bukan membuat network baru, dan membiarkannya tetap ada setelah docker compose down. Key name: yang terpisah lebih penting daripada yang terlihat: tanpa key tersebut, Compose mencari network yang namanya persis edge. Dengan key tersebut, Anda dapat menggunakan satu nama network di file dan nama lain di host.
Jika network tidak ada, Compose menolak untuk memulai dan melaporkan bahwa network telah dideklarasikan sebagai eksternal tetapi tidak ditemukan. Buat network tersebut terlebih dahulu.
Perhatikan penggunaan internal dalam file aplikasi. Database hanya berada di network lokal project tersebut, sehingga proxy tidak dapat menjangkaunya dan hanya app yang dapat mengaksesnya. Menambahkan internal: true di bawah network membatasi lebih jauh dengan menghapus rute network tersebut ke dunia luar sepenuhnya. Ini merupakan pengaturan default yang baik untuk database, tetapi ada konsekuensi yang perlu diketahui sebelum menerapkannya: container pada network internal tidak dapat mengunduh apa pun, sehingga entrypoint yang menjalankan apt-get update atau pip install saat startup akan berhenti dan kemudian gagal karena timeout.
Untuk contoh konfigurasi lengkap dengan aturan routing dan sertifikat, lihat menjalankan beberapa aplikasi di balik satu instance Traefik.
Port yang dipublikasikan melewati UFW
Ini adalah bagian dari jaringan Compose yang dapat berubah menjadi insiden keamanan. Anda memublikasikan sebuah port, memastikan UFW aktif dan menolak semua koneksi kecuali SSH, tetapi layanan tersebut tetap dapat diakses dari internet.
sudo ufw status
curl http://203.0.113.10:8080UFW menyatakan bahwa port tersebut diblokir. curl tetap mengembalikan halaman. Tidak ada yang rusak. Docker menulis aturan penerjemahan alamat dan penerusan miliknya sendiri langsung ke iptables. Lalu lintas menuju port container yang dipublikasikan diteruskan ke container, bukan dikirimkan ke host. Karena itu, lalu lintas tersebut tidak melewati chain yang dikelola UFW untuk lalu lintas yang ditujukan secara lokal. Aturan Docker juga dicocokkan sebelum aturan UFW.
Perbaikan singkatnya adalah memublikasikan port hanya pada alamat yang diperlukan:
ports:
- "127.0.0.1:8080:80"Dengan demikian, sisi host terikat ke loopback. Port tersebut dapat diakses dari server itu sendiri dan melalui tunnel SSH, tetapi tidak dari tempat lain. Tempatkan titik masuk publik di belakang reverse proxy yang memang memublikasikan port 80 dan 443. Penjelasan lengkapnya, termasuk chain DOCKER-USER untuk kasus ketika Anda harus memfilter port yang dipublikasikan, tersedia di mengapa Docker memublikasikan port langsung melewati UFW dan cara memperbaikinya.
Cara men-debug masalah ini dengan empat perintah
Mulailah dengan memeriksa jaringan yang sebenarnya digunakan setiap container:
docker network inspect shop_defaultBlok Containers mencantumkan setiap container yang terhubung beserta alamatnya. Service yang tidak ada dalam daftar tersebut berada pada jaringan lain, menggunakan host mode, atau tidak sedang berjalan.
Uji resolusi nama dari container sementara yang terhubung ke jaringan yang sama. Dengan cara ini, Anda tidak memerlukan tool apa pun di dalam image Anda sendiri:
docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432nslookup yang gagal menunjukkan masalah pada resolusi nama atau keanggotaan jaringan. Jika nslookup berhasil, tetapi nc gagal, berarti nama dan jaringan sudah benar. Masalahnya berada pada koneksi itu sendiri. Jika nc melaporkan Connection refused, penyebab yang umum adalah service tidak listening pada port tersebut, atau listening pada 127.0.0.1 di dalam container sendiri, bukan pada 0.0.0.0. Hal ini sering terjadi pada server development. Perbaikannya berada pada alamat binding aplikasi, bukan pada Docker. Jika nc berhenti dan mengalami timeout, cari sesuatu yang menjatuhkan trafik dalam perjalanan, seperti aturan firewall, atau container yang dijeda atau tidak merespons.
Ada satu masalah lain yang tampak seperti bug Docker. Jika container dapat berkomunikasi satu sama lain tetapi tidak dapat mengakses mesin pada jaringan kantor atau VPN, kemungkinan subnet Docker bertumpang tindih dengan jaringan tersebut. Secara default, Docker mengalokasikan subnet mulai dari 172.17.0.0/16. Pindahkan pool pada /etc/docker/daemon.json:
{
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}Kemudian jalankan sudo systemctl restart docker dan buat ulang jaringan yang terdampak, karena jaringan yang sudah ada tetap menggunakan subnet saat pertama kali dibuat.
FAQ
Mengapa container saya tidak dapat saling menjangkau berdasarkan nama layanan?
Keduanya tidak berada di jaringan yang sama. Compose menempatkan setiap layanan di <project>_default secara otomatis, tetapi saat Anda menambahkan daftar networks: ke suatu layanan, daftar tersebut menjadi seluruh jaringan untuk layanan itu dan jaringan default tidak lagi diterapkan secara implisit. Jalankan docker network inspect <network> dan pastikan kedua container muncul di blok Containers. Periksa juga bahwa tidak ada layanan yang menggunakan network_mode: host, karena container dalam mode host tidak berada di jaringan Docker mana pun dan tidak dapat menemukan nama layanan.
Apakah saya perlu memublikasikan port agar satu layanan dapat menjangkau layanan lain?
Tidak. Di jaringan Compose, setiap port pada setiap container dapat dijangkau oleh container lain di jaringan tersebut. ports: hanya digunakan untuk mengekspos container agar menerima traffic dari luar Docker, sedangkan expose: adalah dokumentasi. Memublikasikan port database merupakan kebiasaan yang umum dan berisiko, karena database menjadi tersedia melalui antarmuka publik server Anda.
Apa perbedaan antara jaringan bridge dan host?
Bridge memberi container namespace jaringan dan alamatnya sendiri pada switch virtual, dengan resolusi nama otomatis antarkontainer serta traffic keluar yang diterjemahkan. Host memberi container akses langsung ke stack jaringan host: tidak ada alamat terpisah, tidak ada resolusi berdasarkan nama layanan, tidak ada publikasi port, dan tidak ada isolasi dari listener lain pada host. Bridge adalah default dan pilihan yang tepat, kecuali proses tersebut memerlukan antarmuka milik host.
Bagaimana cara menghubungkan container dari dua file Compose yang berbeda?
Buat jaringan bersama dengan docker network create edge, lalu deklarasikan jaringan tersebut di kedua file menggunakan external: true dan hubungkan layanan yang perlu berkomunikasi. Compose tidak akan membuat atau menghapus jaringan tersebut. Jika langkah pembuatan dilewati, Compose menolak untuk memulai dan melaporkan bahwa jaringan telah dideklarasikan sebagai eksternal tetapi tidak ditemukan.
Mengapa container saya dapat dijangkau dari internet ketika UFW memblokir port tersebut?
Karena port yang dipublikasikan ditangani oleh aturan penerusan yang ditambahkan Docker ke iptables. Aturan tersebut dicocokkan sebelum aturan UFW, dan traffic yang diteruskan juga tidak melewati chain yang difilter UFW. Ikat sisi host ke loopback menggunakan "127.0.0.1:8080:80" dan tempatkan semua layanan publik di belakang reverse proxy pada port 80 dan 443.