Cara Merutekan Container Docker melalui VPN
Port container hilang saat memakai Gluetun karena namespace jaringan bersama. Pahami penyebabnya, error yang muncul, dan cara publikasi port di Docker Compose v3.41.3.
Mengapa port hilang saat Anda merutekan container Docker melalui VPN
Untuk merutekan container Docker melalui VPN, berikan tunnel kepada satu container, lalu hubungkan container lain ke namespace jaringannya dengan network_mode: "service:gluetun". Bagian penghubungan ini sering mengejutkan pengguna. Container yang terhubung tidak lagi memiliki jaringan sendiri. Karena itu, port yang dipublikasikan dan nama service Docker miliknya ikut hilang. Publikasikan port pada container VPN. Container lain dapat mengakses aplikasi melalui nama container VPN.
Jika Anda membiarkan blok ports: pada container yang terhubung, Docker menolak membuat container tersebut:
Error response from daemon: conflicting options: port publishing and the container type network modeTool yang digunakan di sini adalah Gluetun. Gluetun adalah container yang terhubung ke penyedia VPN komersial melalui WireGuard atau OpenVPN dan memiliki firewall sendiri. Release v3.41.3 merupakan versi terbaru per August 2026. Contoh ini menggunakan Mullvad dengan WireGuard. Karena itu, Anda memerlukan akun dan key dari penyedia VPN. Jika Anda ingin mengakhiri tunnel pada hardware milik sendiri, menjalankan server WireGuard sendiri pada VPS akan membangun sisi lainnya, sedangkan wg-easy dalam Docker menyediakan antarmuka web untuk konfigurasi tersebut.
Hal yang sebenarnya dilakukan network_mode: "service:gluetun"
Setiap container Docker biasanya mendapatkan namespace jaringan sendiri: interface, tabel routing, aturan firewall, dan socket listening sendiri. Mode service: melewati langkah tersebut dan menjalankan container di dalam namespace gluetun. Satu namespace berarti satu alamat IP, dan hal ini mengubah enam hal.
- Aplikasi tidak memiliki alamat sendiri. Alamatnya adalah alamat gluetun.
- Aplikasi tidak terhubung ke jaringan Docker mana pun, sehingga nama servicenya tidak pernah didaftarkan dan tidak pernah dapat di-resolve. Container lain harus menggunakan
gluetun. - Container di dalam namespace tersebut saling terhubung melalui
localhost. - Dua container dalam satu namespace tidak dapat melakukan listening pada port yang sama. Dokumentasi Gluetun menyatakan hal ini secara tegas: tidak ada solusi alternatif.
- Capability dimiliki oleh container, bukan oleh namespace. Gluetun memiliki
NET_ADMINdan/dev/net/tunkarena membuat interface tunnel. Container yang terhubung tidak mewarisi capability tersebut. - Compose menolak file apa pun jika salah satu service menetapkan
network_modedannetworkssekaligus. Hubungkan gluetun ke jaringan Anda, lalu aplikasi akan ikut menggunakannya.
Memulai ulang gluetun akan memutus semua yang terhubung kepadanya. Perilaku ini telah didokumentasikan dan menjadi alasan gluetun memulai ulang proses VPN di dalam container, bukan keluar ketika koneksi gagal. Setelah Anda memulai ulang atau membuat ulang gluetun, mulai ulang container yang terhubung kepadanya.
File compose yang berfungsi
services:
gluetun:
image: qmcgaw/gluetun:v3
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=mullvad
- VPN_TYPE=wireguard
- SERVER_CITIES=Amsterdam
- TZ=Europe/Amsterdam
env_file:
- ./gluetun.env
volumes:
- ./gluetun:/gluetun
ports:
- 127.0.0.1:8080:8080/tcp
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
network_mode: "service:gluetun"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- WEBUI_PORT=8080
volumes:
- ./qbittorrent:/config
- ./downloads:/downloads
depends_on:
gluetun:
condition: service_healthy
restart: unless-stoppedTag :v3 adalah rilis stabil terbaru dalam seri v3. Tag :latest menunjuk ke commit terakhir pada branch master, yaitu versi pengembangan, jadi tetapkan :v3 pada mesin yang tidak ingin Anda gunakan untuk melakukan debug secara mendadak.
WEBUI_PORT=8080 harus sama dengan port yang dipublikasikan, karena qBittorrent melakukan bind di dalam namespace gluetun dan aturan publish meneruskan trafik dari host ke port 8080 di namespace tersebut. Jika salah satu angka diubah tanpa mengubah angka lainnya, port tidak akan merespons. 127.0.0.1:8080:8080 mempertahankan antarmuka web pada alamat loopback host. 8080:8080 tanpa alamat akan memublikasikan port pada setiap antarmuka dan membuat aturan firewall sendiri. Inilah penyebab port yang dipublikasikan Docker melewati ufw secara langsung.
Jalankan stack tersebut, lalu periksa dengan urutan berikut:
docker compose up -d
docker compose ps
docker compose logs gluetun | tail -30docker compose ps harus menampilkan gluetun sebagai healthy dan qbittorrent sebagai running. Selanjutnya, pastikan alamat keluar dari dalam namespace. Pemeriksaan ini menentukan hasil semua pemeriksaan berikutnya:
docker run --rm --network=container:gluetun alpine:3.22 sh -c "apk add wget && wget -qO- https://ipinfo.io"Kolom ip dalam JSON tersebut harus berisi alamat penyedia VPN Anda. Jika berisi alamat server Anda sendiri, aplikasi tidak berada di dalam tunnel dan semua hal di bawah ini tidak akan berfungsi seperti yang dijelaskan.
Simpan key di luar file compose
gluetun.env menyimpan kredensial dan tidak dimasukkan ke git:
WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32Kedua nilai tersebut berasal dari file konfigurasi WireGuard yang Anda buat di area akun provider. Atur mode file ke 600. Jelaskan manfaatnya secara tepat: key tidak masuk ke repository, tetapi docker inspect gluetun tetap menampilkan semua variabel environment kepada siapa pun yang dapat mengakses Docker socket. File environment dan secret di Docker Compose membahas opsi yang lebih aman.
Cara container di luar tunnel berkomunikasi dengan container di dalamnya
Kedua arah tersebut berfungsi, dan masing-masing menggunakan nama yang berbeda. Kedua container memerlukan jaringan Docker bersama, yaitu jaringan gluetun, karena container yang terhubung tidak memiliki jaringan sendiri. Cara jaringan Docker Compose terhubung menjelaskan konfigurasi defaultnya.
Untuk komunikasi dari luar ke dalam, gunakan nama gluetun dan port yang digunakan aplikasi untuk menerima koneksi. Container reverse proxy dapat mengakses antarmuka web qBittorrent di gluetun:8080. Anda tidak memerlukan entri ports: untuk itu karena trafik antar-container tetap berada di jaringan Docker dan tidak pernah melewati port host.
Untuk komunikasi dari dalam ke luar, gunakan nama service container lain, misalnya postgres:5432. Gluetun dapat me-resolve nama container lain dari dalam namespace-nya sejak v3.41. Gunakan versi tersebut atau yang lebih baru jika sebuah nama tidak dapat di-resolve.
Firewall Gluetun menentukan pihak yang boleh membuka koneksi ke Gluetun. Trafik dari jaringan Docker milik gluetun sendiri diizinkan. Klien pada subnet lain, seperti laptop di LAN atau container pada jaringan bridge terpisah, akan dibuang sampai Anda mencantumkan subnet tersebut:
FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24Makna yang didokumentasikan bersifat spesifik: subnet yang dipisahkan dengan koma dan diizinkan untuk diakses oleh Gluetun serta container yang menggunakan network stack-nya.
Koneksi masuk dari Internet merupakan masalah yang berbeda. Peer milik client torrent datang dari sisi VPN, sehingga mem-publish port 6881 pada host tidak memberikan efek apa pun. Anda memerlukan port yang diteruskan oleh provider, lalu mencantumkan port tersebut di FIREWALL_VPN_INPUT_PORTS. Entri ini mengizinkan port dari sisi server VPN. Inilah bagian yang paling sering dibiarkan tidak berfungsi oleh media stack yang dibuat dengan Docker Compose.
Sakelar pemutus: apa yang terjadi saat tunnel terputus
Pola ini sepadan dengan kompleksitasnya ketika terjadi kegagalan. Container yang terpasang tidak memiliki rute kedua. Satu-satunya jalur keluar dari mesin adalah namespace yang digunakan bersama, sehingga saat tunnel terputus tidak ada jalur cadangan. Firewall Gluetun menerapkan aturan yang sama dari sisi lain: trafik keluar harus melalui tunnel atau menuju endpoint server VPN, sedangkan semua trafik lainnya dibuang. Tidak ada jeda ketika paket dapat keluar melalui interface biasa saat client melakukan koneksi ulang.
Gluetun memantau koneksinya sendiri. Setiap menit, Gluetun mengirim ICMP echo (ping) ke alamat dalam HEALTH_ICMP_TARGET_IPS, yang secara default adalah 1.1.1.1,8.8.8.8. Setiap lima menit, Gluetun membuat koneksi TCP dan TLS (transport layer security) penuh ke HEALTH_TARGET_ADDRESSES, dengan nilai default cloudflare.com:443,github.com:443. Jika pemeriksaan tersebut gagal, Gluetun me-restart VPN di dalam container dan mencatatnya di log:
WARN [vpn] restarting VPN because it failed to pass the healthcheck: periodic check: dialing: dial tcp4: lookup cloudflare.com: i/o timeoutBaca log container yang terpasang dengan mempertimbangkan urutan tersebut. Baris seperti connection refused, operation not permitted, dan i/o timeout di dalam aplikasi merupakan akibat tunnel yang terputus, bukan penyebabnya. Dokumentasi Gluetun menyatakan hal ini secara langsung, karena orang sering melaporkan akibatnya lalu menghabiskan waktu berjam-jam untuk mencari penyebab yang keliru.
HEALTH_RESTART_VPN=on adalah konfigurasi default dan sebaiknya tetap aktif. Nonaktifkan hanya saat men-debug satu kegagalan tertentu, karena jika dinonaktifkan, tunnel yang terputus akan tetap terputus.
Urutan: hentikan stack agar tidak dimulai sebelum tunnel aktif
Image ini menyediakan healthcheck Docker:
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheckPerintah tersebut menjalankan salinan gluetun kedua yang berumur singkat. Salinan ini meminta status health dari instance yang sedang berjalan pada http://127.0.0.1:9999/. Tunnel yang berfungsi merespons dengan 200 OK. Tunnel yang bermasalah merespons dengan 500 Internal server error beserta string error, dan container ditandai sebagai tidak sehat setelah satu kegagalan.
condition: service_healthy digunakan untuk menunggu kondisi tersebut. depends_on: [gluetun] biasa hanya menunggu sampai container dimulai. Proses ini terjadi beberapa detik sebelum handshake selesai. Akibatnya, aplikasi dimulai saat jaringan belum berfungsi dan sering menyerah pada percobaan koneksi pertamanya. Healthcheck di Docker Compose menjelaskan sintaks dan kolom waktu tersebut.
Ada satu batasan yang sering terlewat. Compose mengevaluasi kondisi tersebut satu kali, saat membuat container. Compose tidak menghentikan atau memulai ulang aplikasi jika gluetun kemudian menjadi tidak sehat. Auto-healing internal gluetun menangani kasus ini. Karena itu, gluetun memulai ulang proses VPN, bukan container.
Periksa kebocoran DNS sebelum mempercayai konfigurasi
DNS (domain name system) adalah kebocoran yang tetap terjadi meskipun tunnel sudah dikonfigurasi dengan benar. Gluetun menjalankan resolver sendiri di dalam namespace dan meneruskan kueri melalui DoT (DNS over TLS) ke Cloudflare secara default: DNS_UPSTREAM_RESOLVER_TYPE=dot dan DNS_UPSTREAM_RESOLVERS=cloudflare. Biarkan keduanya tanpa perubahan agar pencarian nama terenkripsi dan berlangsung melalui tunnel.
Pengaturan yang menyebabkan masalah ini adalah DNS_UPSTREAM_PLAIN_ADDRESSES. Pengguna biasanya mengaktifkannya ketika suatu nama gagal di-resolve dan mereka ingin router atau resolver milik provider menjawab kueri tersebut. Dokumentasi Gluetun menjelaskan dampaknya secara langsung: seluruh trafik DNS tidak akan melewati tunnel VPN dan akan bocor ke luar tunnel. Trafik Anda tetap privat. Daftar hostname Anda tidak. Kesalahan yang sama pada versi WireGuard dibahas dalam DNS yang berhenti di-resolve melalui tunnel WireGuard.
Untuk mengujinya, tetapkan HTTPPROXY=on pada gluetun dan publish 8888:8888/tcp, lalu arahkan browser ke proxy tersebut dan buka pengujian kebocoran DNS. Hasilnya harus mencantumkan provider Anda atau Cloudflare, bukan router rumah Anda. Dokumentasi Gluetun juga memperingatkan bahwa beberapa pengujian kebocoran dapat menampilkan hasil yang tidak biasa karena resolver di dalam namespace adalah perantara caching lokal, bukan server yang akhirnya memberikan jawaban. Anggap negara yang salah atau resolver ISP Anda sendiri sebagai indikator kebocoran yang sebenarnya.
Menambahkan Tailscale di samping VPN sidecar, dan menentukan mana yang berlaku
Tailscale adalah jaringan overlay yang dibangun di atas WireGuard untuk mengakses mesin milik Anda. Banyak orang menjalankannya di samping VPN provider agar tetap memiliki jalur administratif ke dalam stack. Keduanya jarang saling bertentangan karena alasan yang perlu dipahami. Dokumentasi Tailscale menyatakan perilaku defaultnya: Tailscale berfungsi sebagai jaringan overlay, hanya merutekan trafik antara perangkat yang menjalankan Tailscale, dan tidak memengaruhi trafik Internet publik Anda.
Jadi, jawabannya bergantung pada satu pengaturan.
- Tailscale dalam container sendiri dengan konfigurasi default: Tailscale tidak pernah melihat trafik keluar aplikasi. Gluetun membawa seluruh trafik tersebut. Tailscale mengakses aplikasi melalui
gluetun:8080, sama seperti container eksternal lainnya. - Tailscale dipasang ke namespace milik gluetun dengan
network_mode: "service:gluetun": Tailscale memerlukancap_addsendiri untuknet_admindannet_raw, karena capability tidak ikut terbawa bersama namespace. Dalam mode userspace networking default,TS_USERSPACEaktif, tailscaled tidak membuat interface sama sekali dan bekerja sebagai proxy SOCKS5 atau HTTP, sehingga tidak dapat mengubah routing. Gluetun tetap membawa seluruh trafik. - Konfigurasi yang sama dengan
TS_USERSPACE=false: tailscaled membuat perangkat tunnel dan memasang route, tetapi hanya untuk rentang tailnet100.64.0.0/10serta route subnet apa pun yang Anda iklankan denganTS_ROUTES. Trafik publik tetap keluar melalui gluetun. - Salah satu konfigurasi di atas dengan exit node yang dipilih,
sudo tailscale set --exit-node=<exit-node-ip>: Tailscale mengambil alih default route. Tailscale menjadi pemiliknya. Jangan gabungkan konfigurasi ini dengan gluetun. Satu default route hanya boleh memiliki satu pemilik.
Salah satu dampaknya terlihat ketika Tailscale berjalan di dalam tunnel. Peer Tailscale akan melihat alamat milik VPN provider, sehingga koneksi lebih sering beralih ke relay. tailscale status menampilkan relay "..." di samping peer, bukan direct, jika hal itu terjadi. Koneksi tetap berfungsi, tetapi lebih lambat. Jika overlay adalah satu-satunya hal yang benar-benar Anda perlukan, perbedaan antara WireGuard biasa dan Tailscale adalah titik awal yang lebih baik.
Hal yang gagal dan pesan yang akan Anda lihat
Docker menolak membuat container aplikasi. Error response from daemon: conflicting options: port publishing and the container type network mode berarti blok ports: masih terpasang pada service yang terhubung. Pindahkan blok tersebut ke gluetun.
Compose menolak seluruh file. Sebuah service tidak dapat menetapkan network_mode dan networks sekaligus. Letakkan network pada gluetun.
Container lain tidak dapat menemukan aplikasi. curl: (6) Could not resolve host: qbittorrent adalah perilaku yang benar karena container yang terhubung tidak bergabung dengan network mana pun dan tidak mendaftarkan nama. Gunakan gluetun dan port tersebut.
Container terhubung kedua tidak mau start. Dua proses dalam satu namespace tidak dapat melakukan bind pada port yang sama. Proses yang kalah akan melaporkan bahwa alamat tersebut sudah digunakan. Ubah port internal aplikasi atau jalankan gluetun kedua.
Aplikasi tidak memiliki network setelah Anda mengubah gluetun. Me-restart atau membuat ulang gluetun akan memutus konektivitas semua container yang terhubung kepadanya. Restart container-container tersebut.
Halaman kecil dapat dimuat, tetapi halaman besar macet. Itu adalah masalah MTU (maximum transmission unit). Tunnel menambahkan overhead, lalu suatu perangkat dalam jalur membuang paket yang terlalu besar tanpa mengirimkan error. Turunkan WIREGUARD_MTU, coba 1400, lalu 1320.
Gluetun tidak pernah menjadi healthy. Pemeriksaan startup menyebutkan tersangka pertama: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. Periksa apakah key sudah kedaluwarsa, kemudian periksa apakah daftar server sudah usang, lalu pastikan firewall host tidak memblokir UDP keluar.
FAQ
Mengapa port yang dipublikasikan oleh container saya berhenti berfungsi di belakang Gluetun?
Karena network_mode: "service:gluetun" menempatkan container ke dalam network namespace milik gluetun, sedangkan satu namespace memiliki satu alamat IP dan satu kumpulan port yang listening. Aplikasi tetap listening, tetapi aturan publish harus berada pada container yang memiliki namespace tersebut. Pindahkan daftar ports: ke service gluetun. Jika Anda membiarkannya pada service yang terhubung, Docker bahkan tidak akan membuatnya: Error response from daemon: conflicting options: port publishing and the container type network mode.
Bagaimana cara mengakses container di dalam tunnel VPN dari container di luarnya?
Gunakan nama service gluetun dan port yang digunakan aplikasi untuk listening, misalnya gluetun:8080. Container yang terhubung tidak memiliki jaringan Docker sendiri, sehingga namanya tidak pernah dapat di-resolve. Tidak ada port yang perlu dipublikasikan untuk trafik antarkontainer. Sebaliknya, container di dalam namespace dapat mengakses container di luar namespace berdasarkan nama servicenya, seperti postgres:5432, pada Gluetun v3.41 dan yang lebih baru. Klien pada subnet yang berbeda, seperti laptop di LAN Anda, akan diblokir oleh firewall gluetun sampai subnet tersebut ditambahkan ke FIREWALL_OUTBOUND_SUBNETS.
Apakah Gluetun berfungsi sebagai kill switch saat VPN terputus?
Ya, karena dua alasan sekaligus. Container yang terhubung tidak memiliki rute selain rute di namespace bersama, sehingga tunnel yang terputus membuatnya tidak memiliki jalur untuk keluar dari mesin. Firewall Gluetun juga hanya mengizinkan trafik keluar melalui tunnel dan menuju endpoint server VPN. Gluetun kemudian me-restart VPN secara internal dan mencatat WARN [vpn] restarting VPN because it failed to pass the healthcheck, bukan keluar, karena setiap container yang terhubung akan kehilangan jaringannya saat gluetun sendiri di-restart.
Tailscale dan Gluetun dalam stack yang sama: mana yang membawa trafik keluar?
Gluetun, dalam setiap konfigurasi kecuali satu. Secara default, Tailscale hanya merutekan trafik antarperangkat dalam tailnet Anda dan membiarkan trafik publik berjalan langsung. Dalam mode userspace default pada image container, Tailscale tidak membuat interface sama sekali, sehingga tidak dapat memengaruhi routing. Dengan TS_USERSPACE=false, Tailscale hanya memasang rute untuk 100.64.0.0/10 dan subnet yang Anda iklankan. Pengecualiannya adalah exit node: sudo tailscale set --exit-node=<exit-node-ip> menjadikan Tailscale sebagai rute default, sehingga Tailscale yang digunakan. Pilih satu produk untuk mengelola rute default, bukan menumpuk keduanya.