Jaringan Antar-Container yang Sering Bikin Bingung

Salah satu momen paling membingungkan saat mulai serius pakai Docker adalah ketika sebuah container tidak bisa "melihat" container lain, padahal menurut kita keduanya jalan di mesin yang sama. Saya sering menemui pola ini saat membantu tim menyusun stack yang terdiri dari beberapa service — misalnya aplikasi Laravel, database, dan cache — yang masing-masing jalan di container terpisah. Begitu paham konsep dasarnya, sebenarnya ini bukan hal rumit. Masalahnya, konsep ini melawan intuisi yang sudah terbentuk dari kebiasaan development di luar container.

Kenapa localhost di dalam container bukan localhost yang sama

Di luar container, kita terbiasa berpikir localhost atau 127.0.0.1 sebagai "mesin saya sendiri". Begitu masuk ke dunia container, setiap container punya network namespace sendiri — semacam ruang jaringan terisolasi dengan interface loopback-nya sendiri. Artinya, localhost di dalam container A hanya menunjuk ke container A itu sendiri, bukan ke host, dan bukan ke container B yang kebetulan jalan di mesin fisik yang sama.

Ini yang sering bikin orang bingung ketika, misalnya, aplikasi di dalam container mencoba konek ke database dengan alamat 127.0.0.1:5432, padahal database-nya jalan di container terpisah. Dari sudut pandang container aplikasi, port 5432 di localhost-nya sendiri memang tidak ada apa-apa — walaupun di container lain port itu benar-benar terbuka.

Aturan sederhana yang saya pegang: kalau dua proses ada di container yang berbeda, jangan pernah pakai localhost untuk saling bicara. localhost hanya valid untuk komunikasi antar proses di dalam container yang sama.

Bagaimana container saling menemukan lewat nama service

Solusinya adalah DNS internal yang disediakan Docker. Ketika beberapa container digabungkan dalam satu user-defined network — yang otomatis terbentuk kalau kita pakai Docker Compose — Docker menjalankan resolver DNS bawaan yang memetakan nama service ke IP container yang bersangkutan. Jadi kalau di docker-compose.yml kita punya service bernama db, dari container aplikasi kita cukup memanggil host db, bukan IP atau localhost.

services:
  app:
    build: .
    environment:
      DB_HOST: db
  db:
    image: mariadb:11

Di aplikasi, konfigurasi koneksi database tinggal diarahkan ke host db, port default service tersebut (misalnya 3306 untuk MariaDB). Docker akan menerjemahkan nama itu ke alamat IP internal container db saat ini — dan ini tetap bekerja walau container tersebut restart dan dapat IP baru, karena resolusinya berbasis nama, bukan alamat statis.

Satu catatan penting: mekanisme DNS otomatis ini hanya berlaku di user-defined network, bukan di network bridge bawaan (default) yang dipakai kalau kita menjalankan container satu-satu dengan docker run tanpa network eksplisit. Di situ, resolusi nama antar container tidak otomatis tersedia, jadi sebaiknya selalu buat network sendiri, atau andalkan Compose yang sudah melakukannya secara default.

Kapan localhost tetap benar dipakai

Ada satu kasus yang justru sering salah arah: mengakses service dari host (mesin kita, bukan dari dalam container lain). Dari sudut pandang host, service yang portnya sudah di-publish (misalnya ports: ["8080:80"]) memang bisa diakses lewat localhost:8080, karena Docker meneruskan trafik dari port host ke port container. Jadi localhost benar dipakai dari host ke container, tapi salah kalau dipakai dari container ke container lain. Dua arah komunikasi ini gampang tertukar kalau kita tidak sadar sedang berada "di dalam" jaringan mana saat menulis kode atau konfigurasi.

Yang perlu diingat

Kuncinya cuma dua: pertama, localhost di dalam container hanya berarti container itu sendiri. Kedua, cara yang benar untuk container saling bicara adalah lewat nama service, memanfaatkan DNS internal Docker di dalam user-defined network. Begitu dua aturan ini melekat di kepala, sebagian besar error "connection refused" antar service di Docker langsung terasa masuk akal penyebabnya.