SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor

Gluetun: Akses Host dan Container Lain

Container di balik Gluetun tidak memiliki interface sendiri. Publikasikan port pada Gluetun dan buka hanya subnet yang perlu diakses di luar tunnel.

Apa yang terjadi saat sebuah container bergabung dengan network gluetun

Container yang menetapkan network_mode: service:gluetun tidak memiliki network interface sendiri. Container tersebut bergabung ke network namespace milik gluetun. Karena itu, publikasi port dan aturan firewall tidak lagi menjadi properti container tersebut, melainkan menjadi properti service gluetun. Semua jawaban di bawah ini berasal dari satu fakta tersebut.

Network namespace adalah salinan privat network stack milik kernel. Namespace memiliki interface, routing table, aturan firewall, dan listening socket sendiri. Docker secara default memberikan satu namespace kepada setiap container. Saat Anda menulis network_mode: service:gluetun, Docker melewati langkah tersebut dan menempatkan container baru ke dalam namespace yang sudah dimiliki gluetun. Container tetap memiliki filesystem sendiri dan file /etc/hosts sendiri. File kedua ini penting pada bagian berikutnya.

Anda dapat melihatnya secara langsung.

docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrent

Perintah tersebut menampilkan container:, diikuti oleh container ID gluetun. Pada container biasa, perintah tersebut akan menampilkan bridge. Panduan ini melanjutkan bagian mengarahkan trafik Docker melalui VPN dengan gluetun: tunnel sudah berfungsi, tetapi sekarang tidak ada yang dapat berkomunikasi dengan container tersebut.

Publikasikan port pada gluetun, bukan pada aplikasi

Biarkan blok ports: pada service yang menetapkan network_mode, dan Docker akan menolak membuat container:

Error response from daemon: conflicting options: port publishing and the container type network mode

Alasannya langsung. Publikasi port berarti menambahkan aturan NAT (network address translation) yang meneruskan port pada host ke namespace jaringan milik container. Container ini tidak memiliki namespace jaringan sendiri. Pindahkan pemetaan tersebut ke service gluetun. Nomor port tidak berubah karena aplikasi tetap listening pada port tersebut di dalam namespace yang digunakan bersama.

services:
  gluetun:
    ports:
      - "8080:8080/tcp"   # qBittorrent web UI

  qbittorrent:
    network_mode: "service:gluetun"
    # no ports: block here

Blok expose: pada service dependen juga tidak berguna. Blok networks: di sana bahkan akan menghentikan proses: Compose melaporkan bahwa service tersebut mendeklarasikan network_mode dan networks yang saling eksklusif, lalu menolak memuat file itu sama sekali.

Dampak lain muncul kemudian. Semua container dalam namespace tersebut berbagi satu ruang port. Karena itu, dua aplikasi yang sama-sama menggunakan 8080 secara default akan mengalami konflik, dan aplikasi kedua yang dijalankan gagal dengan error address already in use. Ubah salah satunya dalam konfigurasi aplikasinya sendiri, misalnya variabel WEBUI_PORT pada image LinuxServer qBittorrent, lalu publikasikan nomor baru tersebut pada gluetun.

Bagaimana container di belakang gluetun saling terhubung?

Di dalam namespace tersebut, container sudah berbagi antarmuka loopback. Container di belakang gluetun dapat menjangkau container saudaranya melalui 127.0.0.1:<port> tanpa melibatkan jaringan Docker.

Dari luar namespace, container tersebut tidak memiliki nama. DNS bawaan Docker menerjemahkan nama service menjadi alamat service itu pada jaringan yang ditentukan pengguna, sedangkan container ini tidak memiliki alamat pada jaringan mana pun. Karena itu, container biasa seperti Sonarr tidak menjangkau klien torrent melalui http://qbittorrent:8080. Container tersebut menjangkaunya melalui http://gluetun:8080, karena socket sedang listening di namespace gluetun, pada alamat gluetun. Hal ini mengejutkan pengguna yang memahami cara kerja jaringan dan nama service Docker Compose dan mengharapkan penamaan biasa tetap berlaku. Cara ini juga berfungsi tanpa memublikasikan apa pun ke host, karena kedua container berada pada jaringan Compose yang sama.

Periksa DNS sebelum melakukan debugging lainnya. Gluetun menjalankan resolver sendiri dan menulis ulang /etc/resolv.conf di dalam containernya sendiri, tetapi /etc/resolv.conf adalah file per container. Jadi, file yang ditulis gluetun bukan file yang dibaca aplikasi Anda.

docker exec qbittorrent cat /etc/resolv.conf

Bagaimana cara mengakses service yang berjalan pada Docker host?

Gunakan host.docker.internal. Pengaturan ini memerlukan dua konfigurasi di dua tempat berbeda karena ada dua hal berbeda yang perlu diperbaiki.

Nama harus dikonfigurasi terlebih dahulu. /etc/hosts berlaku per container, jadi entri extra_hosts harus ditempatkan pada application container, bukan pada gluetun.

  prowlarr:
    network_mode: "service:gluetun"
    extra_hosts:
      - "host.docker.internal:host-gateway"

host-gateway adalah nilai khusus yang diganti Docker dengan alamat internal host itu sendiri. Pada instalasi Docker Linux biasa, alamat tersebut adalah alamat bridge docker0, yang biasanya berupa 172.17.0.1. Pastikan alamat Anda dengan menjalankan ip -4 addr show docker0 pada VPS. Docker Desktop menyelesaikan nama ini secara otomatis. Karena itu, panduan yang ditulis untuk laptop sering tidak menyertakan baris extra_hosts, lalu file yang sama gagal digunakan pada server.

Rute harus dikonfigurasi setelah itu. Menambahkan nama hanya memberi tahu container alamat yang harus digunakan. Paket tetap keluar melalui rute default gluetun, yaitu tunnel, lalu firewall gluetun menjatuhkannya. Gejalanya adalah koneksi yang menunggu lalu berakhir dengan timeout, bukan koneksi yang ditolak. Penolakan berarti paket tiba dan ada sesuatu yang memberikan respons penolakan. Timeout berarti paket tidak pernah tiba.

  gluetun:
    environment:
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32

Selanjutnya, pastikan service pada host benar-benar listening pada alamat tersebut. Server PostgreSQL yang hanya terikat pada 127.0.0.1 tidak dapat diakses dari container mana pun, baik melalui tunnel maupun tidak, karena 127.0.0.1 di dalam namespace adalah loopback milik namespace itu sendiri. Ikat service ke 172.17.0.1 sebagai gantinya. Dengan demikian, service menerima koneksi dari container tanpa membuka akses pada interface publik. Verifikasi dengan ss -lntp | grep 5432 pada host.

Apa yang sebenarnya diubah oleh FIREWALL_OUTBOUND_SUBNETS

Dokumentasi gluetun menjelaskannya sebagai subnet yang dipisahkan dengan koma dan boleh diakses oleh gluetun serta container yang berbagi network stack dengannya. Dokumentasi tersebut juga mencatat bahwa pengaturan ini melibatkan perubahan pada firewall dan routing. Kedua aspek tersebut penting. Gluetun menambahkan route untuk setiap subnet yang tercantum melalui gateway Docker bridge, sehingga paket menuju alamat tersebut keluar melalui eth0, bukan melalui tunnel. Gluetun juga membuka firewall untuk subnet tersebut karena jika tidak, gluetun akan menjatuhkan trafik outbound yang tidak menuju server VPN.

Tulis nilainya tanpa spasi setelah koma.

      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32

Dua hal mudah terlewat. Pengaturan ini berlaku pada tingkat namespace, sehingga diterapkan ke setiap container di belakang gluetun, bukan hanya container yang Anda maksud. Pengaturan ini juga hanya berlaku untuk outbound: pengaturan ini mengatur koneksi yang dimulai oleh container. Koneksi yang masuk melalui port yang dipublikasikan menggunakan jalur berbeda dan tidak memerlukan entri di sini.

Mengakses UI web dari peer Tailscale

Tailscale memberikan setiap mesin alamat dalam 100.64.0.0/10, yaitu rentang yang dicadangkan untuk carrier-grade NAT. Tidak ada biaya untuk fitur ini pada tailnet pribadi, tetapi batas enam pengguna pada paket gratis menentukan apakah kondisi tersebut tetap berlaku ketika orang lain memerlukan akses ke UI yang sama. Jumlah perangkat tidak dibatasi pada semua paket. Karena penagihan menghitung jumlah pengguna, bukan jumlah mesin, biaya sebenarnya tailnet berbayar bergantung pada jumlah orang yang diundang, bukan jumlah container yang Anda ekspos kepada mereka. Kedua arah tersebut memerlukan konfigurasi yang berbeda.

Arah inbound lebih sederhana. Mempublikasikan 8080:8080 pada gluetun akan mengikat port tersebut pada semua alamat host. Interface tailscale0 milik host juga termasuk di dalamnya, sehingga peer dapat membuka http://<machine-name>:8080 dan mengakses container. Gluetun tidak berperan dalam jalur ini karena aturan NAT Docker berada pada host, di luar namespace.

Agar UI hanya dapat diakses melalui tailnet, ikat port yang dipublikasikan ke alamat Tailscale milik host, bukan ke semua alamat.

    ports:
      - "100.101.102.103:8080:8080/tcp"

Temukan alamat tersebut dengan tailscale ip -4 pada host. Binding merupakan kontrol yang lebih kuat daripada aturan firewall dalam kasus ini karena port tersebut sama sekali tidak dibuka pada interface publik. Jika Anda lebih memilih mengakses UI melalui nama HTTPS daripada host dan port, tailscale serve dapat meneruskan koneksi ke port yang dipublikasikan tersebut. Namun, serve dan funnel berbeda dalam hal siapa yang akhirnya dapat mengaksesnya, dan hanya salah satunya yang mempertahankan UI di dalam tailnet. Cara ini juga menghindari masalah pada publikasi port Docker yang melewati ufw secara langsung. Pola sidecar Tailscale untuk Compose menghilangkan port host sepenuhnya, tetapi cara kerjanya adalah menempatkan aplikasi di namespace jaringan sidecar. Container di belakang gluetun sudah menggunakan satu-satunya network_mode pada gluetun.

Arah outbound adalah bagian yang memerlukan FIREWALL_OUTBOUND_SUBNETS. Jika container harus memanggil peer, tambahkan alamat peer tersebut. Gunakan /32 per peer, bukan seluruh /10. Jika mesin yang Anda panggil berada di jaringan privat yang diakses melalui VPS yang mengiklankan subnet tersebut ke tailnet Anda, cantumkan rentang yang diiklankan, bukan alamat 100.x milik router. Pastikan host sendiri telah menerima rute tersebut. Nama MagicDNS tidak akan di-resolve di dalam container karena container tidak menggunakan resolver milik host. Karena itu, gunakan alamat 100.x numerik atau tetapkan alamat tersebut dengan baris extra_hosts. Hal yang sama berlaku ketika Anda menjalankan server kontrol Tailscale sendiri dengan Headscale.

File compose lengkap untuk pola umum

Klien download di balik VPN, dua UI web yang hanya merespons melalui tailnet, dan satu container yang membaca database PostgreSQL yang berjalan pada host.

services:
  gluetun:
    image: qmcgaw/gluetun:latest
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    ports:
      - "127.0.0.1:8000:8000/tcp"        # gluetun control server, host only
      - "100.101.102.103:8080:8080/tcp"  # qBittorrent UI, tailnet only
      - "100.101.102.103:9696:9696/tcp"  # Prowlarr UI, tailnet only
    volumes:
      - ./gluetun:/gluetun
    environment:
      - VPN_SERVICE_PROVIDER=mullvad
      - VPN_TYPE=wireguard
      - WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
      - WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
      - SERVER_CITIES=Amsterdam
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,100.64.7.9/32
      - TZ=Europe/Amsterdam
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    network_mode: "service:gluetun"
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Amsterdam
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent:/config
      - /srv/downloads:/downloads
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    network_mode: "service:gluetun"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Amsterdam
      - PROWLARR__POSTGRES__HOST=host.docker.internal
      - PROWLARR__POSTGRES__PORT=5432
      - PROWLARR__POSTGRES__USER=prowlarr
      - PROWLARR__POSTGRES__PASSWORD=${PROWLARR_DB_PASSWORD}
      - PROWLARR__POSTGRES__MAINDB=prowlarr-main
      - PROWLARR__POSTGRES__LOGDB=prowlarr-log
    volumes:
      - ./prowlarr:/config
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

Baca file ini untuk memahami polanya, bukan nama produknya. Kedua UI dipublikasikan pada gluetun dan di-bind ke alamat tailnet milik host, sehingga keduanya hanya merespons melalui Tailscale. Hanya Prowlarr yang memiliki baris extra_hosts, karena Prowlarr adalah container yang me-resolve nama host.docker.internal. FIREWALL_OUTBOUND_SUBNETS menyebutkan dua alamat tunggal: alamat bridge Docker milik host agar Prowlarr dapat membuka koneksi database, dan satu peer tailnet.

Server PostgreSQL sengaja tidak dicantumkan dalam file ini. Server tersebut berjalan di VPS sebagai system service biasa yang listening pada 172.17.0.1:5432. Lapisan ini sama dengan stack arr pada Docker Compose, tetapi database dipindahkan ke luar Docker.

Simpan private key WireGuard di luar file compose. ${WIREGUARD_PRIVATE_KEY} membaca key dari file .env yang berada di sebelahnya. Pola ini dibahas dalam file env dan secret untuk Docker Compose. Klausul condition: service_healthy menggunakan healthcheck yang sudah disertakan dalam image gluetun, sehingga tidak ada yang dimulai sampai tunnel melaporkan bahwa dirinya sudah aktif. Healthcheck Compose menjelaskan bentuk umumnya.

Publikasi pada setiap alamat, bukan hanya tailnet

Hapus prefix alamat tersebut dan bind port pada 0.0.0.0, yang mencakup IP publik VPS. Lakukan ini hanya di balik firewall yang Anda kendalikan, dan baca catatan ufw di atas terlebih dahulu.

    ports:
      - "8080:8080/tcp"

Pastikan tunnel masih membawa trafik

Jalankan request yang sama dua kali, sekali dari dalam namespace dan sekali dari host, lalu bandingkan hasilnya.

docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.org

Hasil pertama seharusnya menampilkan alamat exit VPN provider Anda. Hasil kedua seharusnya menampilkan alamat VPS. Jika keduanya sama, trafik container tidak melewati tunnel. Semua perbaikan dalam panduan ini tidak akan berguna sampai masalah tersebut diperbaiki.

Tabel routing menunjukkan trafik mana yang melewati tunnel dan mana yang tidak.

docker run --rm --network=container:gluetun alpine:3.22 ip route show

Default route harus mengarah ke interface tunnel, tun0. Di bawahnya, Anda akan melihat satu route untuk setiap entri dalam FIREWALL_OUTBOUND_SUBNETS yang mengarah ke gateway Docker bridge. Route lain yang keluar melalui eth0 adalah trafik yang melewati VPN.

Control server Gluetun dapat melaporkan public IP yang sama melalui port 8000, di /v1/publicip/ip, tetapi hanya setelah Anda menyiapkan autentikasi. Dokumentasi gluetun menyatakan bahwa semua route control server sekarang bersifat private secara default dan tidak ada yang dapat diakses secara publik. Karena itu, request biasa ke path tersebut akan gagal. Konfigurasikan server menggunakan auth file yang di-bind mount pada /gluetun/auth/config.toml melalui metode basic atau apikey, lalu restart gluetun. Compose file di atas sudah me-mount ./gluetun pada /gluetun, sehingga file tersebut harus ditempatkan di ./gluetun/auth/config.toml pada host. Variabel HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE adalah cara yang lebih cepat, dan dokumentasi sangat menyarankan agar variabel tersebut tidak diatur ke none. Pada release sebelum v3.39.1, server belum dibatasi dengan cara ini. Dokumentasi memperingatkan Anda agar mempercayai apa pun yang dapat menjangkaunya.

Kebocoran yang ditimbulkan oleh satu subnet yang salah

FIREWALL_OUTBOUND_SUBNETS adalah celah yang sengaja dibuat pada firewall. Karena itu, ukuran celah sama dengan ukuran risikonya. Berikut empat cara membuatnya terlalu besar:

  • 0.0.0.0/0 mengirimkan semua trafik ke luar tunnel. Dua pemeriksaan IP di atas mendeteksi masalah ini pada proses pertama, karena keduanya akan mengembalikan alamat yang sama.
  • Rentang yang lebih luas daripada target. Membuka 10.0.0.0/8 untuk menjangkau satu mesin pada 10.0.1.7 juga membuka setiap alamat yang mungkin diumumkan oleh peer torrent dalam rentang tersebut. Tulis 10.0.1.7/32.
  • Rentang yang tumpang tindih dengan alamat milik tunnel. Dokumentasi gluetun memperingatkan bahwa kondisi ini membuat gluetun mengirimkan trafik VPN melalui bridge, sehingga port forwarding tidak berfungsi. Periksa nilai WIREGUARD_ADDRESSES sebelum membuka rentang privat apa pun.
  • 100.64.0.0/10 untuk Tailscale. Tindakan ini membuka sekitar empat juta alamat agar satu peer dapat dijangkau. Cantumkan peer yang diperlukan sebagai entri /32.

Ingat bahwa pengaturan ini berlaku untuk seluruh namespace. Membuka subnet agar indexer dapat menjangkau service pada host juga membuka subnet yang sama untuk torrent client yang menggunakan namespace tersebut. Jalankan kembali pemeriksaan IP publik setiap kali mengubah variabel ini, karena hanya pemeriksaan ini yang menunjukkan apakah perubahan tersebut menghasilkan perilaku yang Anda inginkan.

Hal yang Rusak Saat Anda Me-restart gluetun

Gluetun memiliki namespace tersebut, sehingga siklus hidup gluetun sama dengan siklus hidup namespace. Menjalankan container dependen saat gluetun berhenti akan langsung gagal:

Error response from daemon: cannot join network of a non running container

Me-restart gluetun di tempat adalah kegagalan yang lebih sulit terlihat. Container dependen tetap berjalan sementara namespace yang terhubung dengannya dibuat ulang di bawahnya, sehingga docker ps melaporkan bahwa semuanya sehat, tetapi tidak ada layanan yang merespons. Setelah mengubah service gluetun, buat ulang seluruh grup, bukan hanya me-restart salah satu bagiannya.

docker compose up -d --force-recreate

Hal yang sama berlaku untuk pembaruan image. Mengunduh image gluetun baru lalu membuat ulang service tersebut saja membuat container lain tetap menunjuk ke namespace yang sudah tidak ada.

FAQ

Mengapa Docker mengatakan "port publishing dan mode jaringan tipe container"?

Karena blok ports: masih terdapat pada service yang juga menetapkan network_mode: service:gluetun. Publishing port menambahkan aturan NAT yang meneruskan port host ke namespace jaringan milik container, sedangkan container dalam mode ini tidak memiliki namespace jaringan sendiri. Hapus blok ports: dari service tersebut, lalu tambahkan pemetaan yang sama ke service gluetun. Nomor port tetap sama karena aplikasi masih listening pada port tersebut di dalam namespace bersama.

Bagaimana container lain mengakses service yang berada di belakang gluetun?

Container dalam namespace yang sama saling mengakses melalui 127.0.0.1. Container di luar namespace tersebut menggunakan nama service gluetun, sehingga http://gluetun:8080 berfungsi sedangkan http://qbittorrent:8080 tidak. Application container tidak memiliki alamat pada jaringan Docker mana pun, sehingga server DNS internal tidak memiliki alamat yang dapat di-resolve untuk namanya. Publishing port tidak diperlukan untuk akses ini selama kedua container berbagi jaringan Compose.

Apa yang harus saya masukkan ke FIREWALL_OUTBOUND_SUBNETS?

Masukkan hanya alamat tujuan yang harus digunakan container di belakang gluetun untuk memulai koneksi, dengan cakupan sesempit mungkin. Satu mesin ditulis sebagai /32. Dua entri yang umum adalah host Docker pada 172.17.0.1/32 dan satu /32 untuk setiap peer Tailscale yang Anda hubungi. Jangan pernah menambahkan 0.0.0.0/0, dan jangan pernah menambahkan rentang yang tumpang tindih dengan alamat tunnel VPN Anda sendiri. Koneksi masuk ke port yang dipublikasikan tidak memerlukan entri di sini.

Mengapa container tidak dapat me-resolve nama MagicDNS Tailscale saya?

MagicDNS bekerja dengan mengarahkan resolver host ke server DNS Tailscale, sedangkan container tidak menggunakan resolver host. Container menggunakan konfigurasi yang ditentukan oleh /etc/resolv.conf miliknya sendiri; di belakang gluetun, konfigurasi tersebut adalah pengaturan DNS gluetun. Konfirmasikan dengan docker exec <container> cat /etc/resolv.conf. Gunakan alamat numerik 100.x milik peer, atau tetapkan nama tersebut dengan entri extra_hosts pada container itu.

Bagaimana cara memastikan trafik masih melewati VPN?

Jalankan satu request dari dalam namespace dan request yang sama dari host, lalu bandingkan hasilnya. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org harus mengembalikan alamat keluar milik provider VPN Anda, sedangkan curl -s https://api.ipify.org pada VPS mengembalikan alamat VPS. Dua hasil yang sama berarti tunnel tidak membawa trafik container. Ulangi pemeriksaan ini setiap kali Anda mengubah FIREWALL_OUTBOUND_SUBNETS.