Gluetun containers bereikbaar maken via netwerk
Een container achter Gluetun heeft geen eigen netwerkinterface. Leer hoe u poorten publiceert op de Gluetun-service en specifieke subnets opent voor extern netwerkverkeer.
Wat gebeurt er wanneer een container toetreedt tot het netwerk van gluetun
Een container die network_mode: service:gluetun instelt, beschikt niet over eigen netwerkinterfaces. Deze treedt toe tot de netwerk-namespace van gluetun, waardoor het publiceren van poorten en firewallregels geen eigenschappen meer zijn van die container, maar van de gluetun-service. Elk onderstaand antwoord vloeit voort uit dit ene feit.
Een netwerk-namespace is de private kopie van een netwerkstack door de kernel: met eigen interfaces, een eigen routeringstabel, eigen firewallregels en eigen listening sockets. Docker geeft elke container er standaard één. Wanneer u network_mode: service:gluetun schrijft, slaat Docker die stap over en plaatst de nieuwe container in de namespace die al eigendom is van gluetun. De container behoudt zijn eigen bestandssysteem en zijn eigen /etc/hosts-bestand, en dat laatste is later van belang.
U kunt dit direct waarnemen.
docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrentDit drukt container: af, gevolgd door het container-ID van gluetun, terwijl een normale container bridge zou afdrukken. Deze handleiding gaat verder waar Docker-verkeer routeren via een VPN met gluetun ophoudt: de tunnel werkt, en nu kan niets meer communiceren met de container.
Publiceer de poort op gluetun, niet op de applicatie
Laat een ports:-blok staan op de service die network_mode instelt en Docker weigert de container aan te maken:
Error response from daemon: conflicting options: port publishing and the container type network modeDe reden hiervoor is direct. Het publiceren van een poort betekent het toevoegen van een NAT-regel (network address translation) die een hostpoort doorstuurt naar de eigen netwerk-namespace van een container, en deze container heeft er geen. Verplaats de mapping naar de gluetun-service. Het poortnummer verandert niet, omdat de applicatie nog steeds op die poort luistert binnen de gedeelde namespace.
services:
gluetun:
ports:
- "8080:8080/tcp" # qBittorrent web UI
qbittorrent:
network_mode: "service:gluetun"
# no ports: block hereEen expose:-blok op de afhankelijke service is eveneens zinloos, en een networks:-blok op die plek is een harde stop: Compose meldt dat de service wederzijds uitsluitende network_mode en networks declareert, en weigert het bestand in zijn geheel te laden.
Eén consequentie wordt pas later merkbaar. Elke container in de namespace deelt een enkele poortruimte, dus twee applicaties die beide standaard op 8080 luisteren, botsen. De tweede applicatie die start, faalt met een foutmelding dat het adres al in gebruik is. Wijzig er een in de eigen configuratie, bijvoorbeeld via de WEBUI_PORT-variabele op de LinuxServer qBittorrent-image, en publiceer vervolgens het nieuwe nummer op gluetun.
Hoe bereiken containers achter gluetun elkaar?
Binnen de namespace delen zij al een loopback-interface. Een container achter gluetun bereikt zijn sibling op 127.0.0.1:<port> zonder dat hier een Docker-netwerk aan te pas komt.
Van buiten de namespace heeft de container geen naam. De ingebouwde DNS van Docker vertaalt een servicenaam naar het adres van die service op een door de gebruiker gedefinieerd netwerk, en deze container heeft op geen enkel netwerk een adres. Een normale container zoals Sonarr bereikt de torrent-client daarom niet op http://qbittorrent:8080. Deze bereikt de client op http://gluetun:8080, omdat de socket luistert in de namespace van gluetun, op het adres van gluetun. Dit verrast mensen die weten hoe Docker Compose-netwerken en servicenamen werken en verwachten dat de gebruikelijke naamgeving van toepassing is. Het werkt ook zonder poorten naar de host te publiceren, aangezien beide containers zich op hetzelfde Compose-netwerk bevinden.
Controleer DNS voordat u iets anders debugt. Gluetun draait zijn eigen resolver en herschrijft /etc/resolv.conf in zijn eigen container, maar /etc/resolv.conf is een bestand per container, dus het bestand dat gluetun heeft geschreven is niet het bestand dat uw applicatie leest.
docker exec qbittorrent cat /etc/resolv.confHoe bereik ik een service die op de Docker-host draait?
Gebruik host.docker.internal. Dit vereist twee instellingen op twee verschillende locaties, omdat er twee verschillende zaken niet correct zijn geconfigureerd.
De naam komt eerst. /etc/hosts is per container, dus de extra_hosts-vermelding hoort bij de applicatiecontainer, niet bij gluetun.
prowlarr:
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"host-gateway is een speciale waarde die Docker vervangt door een intern adres van de host zelf. Op een standaard Linux Docker-installatie is dat het adres van de docker0-bridge, meestal 172.17.0.1. Bevestig dit adres met ip -4 addr show docker0 op de VPS. Docker Desktop lost deze naam zelf op; daarom slaan handleidingen die op een laptop zijn geschreven de extra_hosts-regel over, waarna hetzelfde bestand op een server faalt.
De route komt als tweede. Het toevoegen van de naam vertelt de container alleen welk adres gebruikt moet worden. Het pakket verlaat de container nog steeds via de standaardroute van gluetun, wat de tunnel is, en de firewall van gluetun blokkeert het. Het symptoom is een verbinding die blijft hangen en vervolgens een time-out geeft, in plaats van een verbinding die wordt geweigerd. Een weigering betekent dat het pakket is aangekomen en dat er een negatief antwoord is ontvangen. Een time-out betekent dat het pakket nooit is aangekomen.
gluetun:
environment:
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32Controleer vervolgens of de host-service daadwerkelijk luistert op dat adres. Een PostgreSQL-server die alleen is gebonden aan 127.0.0.1 is onbereikbaar vanuit elke container, ongeacht de tunnel, omdat 127.0.0.1 binnen de namespace de eigen loopback van die namespace is. Bind de service in plaats daarvan aan 172.17.0.1: dit accepteert verbindingen van containers terwijl de service buiten de publieke interface blijft. Controleer dit met ss -lntp | grep 5432 op de host.
Wat FIREWALL_OUTBOUND_SUBNETS daadwerkelijk wijzigt
De documentatie van gluetun beschrijft dit als de door komma's gescheiden subnetten waartoe gluetun en de containers die de netwerkstack delen toegang hebben, en merkt op dat dit firewall- en routeringswijzigingen met zich meebrengt. Beide aspecten zijn van belang. Gluetun voegt voor elk vermeld subnet een route toe via de Docker bridge-gateway, zodat pakketten voor die adressen via eth0 vertrekken in plaats van via de tunnel. Het opent ook de firewall voor deze subnetten, omdat gluetun anders uitgaand verkeer dat niet bestemd is voor de VPN-server blokkeert.
Voer de waarde in zonder spaties na de komma's.
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32Twee eigenschappen worden gemakkelijk over het hoofd gezien. Dit is een instelling op namespace-niveau, dus deze is van toepassing op elke container achter gluetun, niet alleen op de container die u in gedachten had. Bovendien betreft het alleen uitgaand verkeer: het regelt verbindingen die een container zelf initieert. Verbindingen die binnenkomen op een gepubliceerde poort volgen een ander pad en hoeven hier niet te worden ingevoerd.
De web-UI bereiken vanaf een Tailscale-peer
Tailscale geeft elke machine een adres in 100.64.0.0/10, het bereik dat is gereserveerd voor carrier grade NAT. Voor beide richtingen is een andere aanpak vereist.
Inkomend verkeer is het eenvoudigst. Het publiceren van 8080:8080 op gluetun bindt die poort aan alle adressen van de host, en de tailscale0-interface van de host is daar een van. Een peer opent dus http://<machine-name>:8080 en bereikt de container. Gluetun speelt in dat pad geen rol, omdat de NAT-regel van Docker op de host staat, buiten de namespace.
Om de UI alleen bereikbaar te maken via het tailnet, bindt u de gepubliceerde poort aan het Tailscale-adres van de host in plaats van aan elk adres.
ports:
- "100.101.102.103:8080:8080/tcp"Vind dat adres met tailscale ip -4 op de host. Binding is hier een sterkere controle dan een firewallregel, omdat de poort op de publieke interface helemaal niet wordt geopend. Het omzeilt ook het probleem in Docker publiceert poorten direct voorbij ufw.
Uitgaand verkeer is waar FIREWALL_OUTBOUND_SUBNETS terugkeert. Als een container een peer moet aanroepen, voeg dan het adres van die peer toe en geef de voorkeur aan een /32 per peer boven het gehele /10. MagicDNS-namen worden niet omgezet binnen de container, omdat de container de resolver van de host niet gebruikt. Gebruik daarom het numerieke 100.x-adres of zet het vast met een extra_hosts-regel. Hetzelfde geldt wanneer u uw eigen Tailscale-controlserver met Headscale draait.
Een compleet compose-bestand voor de gangbare opzet
Een downloadclient achter de VPN, twee web-interfaces die alleen op de tailnet reageren, en één container die een PostgreSQL-database op de host uitleest.
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-stoppedBestudeer het bestand voor het patroon in plaats van de productnamen. Beide web-interfaces worden gepubliceerd via gluetun en gebonden aan het tailnet-adres van de host, waardoor ze alleen via Tailscale bereikbaar zijn. Alleen Prowlarr bevat de regel extra_hosts, omdat Prowlarr de container is die host.docker.internal oplost. FIREWALL_OUTBOUND_SUBNETS benoemt twee specifieke adressen: het Docker-bridge-adres van de host, zodat Prowlarr een databaseverbinding kan openen, en één tailnet-peer.
De PostgreSQL-server is bewust weggelaten uit het bestand. Deze draait op de VPS als een reguliere systeemservice die luistert op 172.17.0.1:5432. Dit is dezelfde gelaagdheid als een arr-stack op Docker Compose, waarbij de database buiten Docker is geplaatst.
Houd de WireGuard private key buiten het compose-bestand. ${WIREGUARD_PRIVATE_KEY} leest vanuit een .env-bestand in dezelfde map, een patroon dat wordt behandeld in env-bestanden en secrets voor Docker Compose. De condition: service_healthy-clausule gebruikt de healthcheck die standaard in de gluetun-image zit, zodat er niets start voordat de tunnel als actief wordt gerapporteerd. Compose healthchecks leggen de algemene vorm uit.
Publiceren op elk adres in plaats van alleen op het tailnet
Verwijder het adres-prefix en de poort-binds bij 0.0.0.0, wat ook het publieke IP-adres van de VPS omvat. Doe dit alleen achter een firewall die u zelf beheert en lees eerst de opmerking over ufw hierboven.
ports:
- "8080:8080/tcp"Bevestig dat de tunnel verkeer verwerkt
Voer hetzelfde verzoek twee keer uit, één keer vanuit de namespace en één keer vanaf de host, en vergelijk de resultaten.
docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.orgDe eerste opdracht moet het exit-adres van uw VPN-provider tonen. De tweede moet het adres van de VPS tonen. Als deze overeenkomen, gaat het verkeer van de container niet door de tunnel. Elke correctie in deze handleiding is dan zinloos totdat dit is opgelost.
De routeringstabel toont welk verkeer wel en niet via de tunnel verloopt.
docker run --rm --network=container:gluetun alpine:3.22 ip route showDe standaardroute moet naar de tunnelinterface wijzen, tun0. Daaronder ziet u één route per item in FIREWALL_OUTBOUND_SUBNETS, die naar de gateway van de Docker-bridge wijst. Elke andere route die via eth0 verloopt, is verkeer dat de VPN omzeilt.
De control server van Gluetun rapporteert hetzelfde publieke IP-adres op poort 8000, op /v1/publicip/ip. Recente versies vereisen dat u authenticatie configureert voor routes van de control server; stel dit dus in voordat u hiervan afhankelijk bent.
Het lek dat een onjuist subnet veroorzaakt
FIREWALL_OUTBOUND_SUBNETS is een gat dat u bewust in de firewall slaat; de grootte van het gat bepaalt de omvang van het risico. Hier zijn vier manieren om dit te groot te maken:
0.0.0.0/0stuurt al het verkeer buiten de tunnel om. De twee IP-controles hierboven detecteren dit bij de eerste uitvoering, omdat ze hetzelfde adres zullen retourneren.- Een bereik dat breder is dan het doel. Het openen van
10.0.0.0/8om één machine op10.0.1.7te bereiken, opent ook elk adres dat een torrent-peer in dat bereik zou kunnen adverteren. Gebruik10.0.1.7/32. - Een bereik dat overlapt met de eigen adressen van de tunnel. De documentatie van gluetun waarschuwt dat gluetun hierdoor VPN-verkeer via de bridge verstuurt, wat port forwarding verstoort. Controleer uw
WIREGUARD_ADDRESSES-waarde voordat u een privaat bereik opent. 100.64.0.0/10voor Tailscale. Dat zijn ongeveer vier miljoen adressen die worden geopend om één peer te kunnen bereiken. Specificeer de benodigde peers als/32-vermeldingen.
Onthoud dat de instelling de gehele namespace beslaat. Het openen van een subnet zodat een indexer een host-service kan bereiken, opent hetzelfde subnet voor de torrent-client die die namespace deelt. Voer de publieke IP-controle opnieuw uit na elke wijziging van deze variabele, aangezien dit de enige test is die aantoont of de wijziging het beoogde effect had.
Wat gaat er mis bij het herstarten van gluetun
Gluetun beheert de namespace; de levenscyclus van gluetun is daarom gelijk aan die van de namespace. Het starten van een afhankelijke container terwijl gluetun offline is, mislukt direct:
Error response from daemon: cannot join network of a non running containerHet herstarten van gluetun op zijn plek is een minder opvallende fout. De afhankelijke containers blijven draaien terwijl de namespace waaraan ze gekoppeld zijn onder hen opnieuw wordt opgebouwd. Hierdoor rapporteert docker ps dat alles in orde is, terwijl er geen antwoord komt. Na elke wijziging aan de gluetun-service moet de gehele groep opnieuw worden aangemaakt in plaats van slechts één onderdeel te herstarten.
docker compose up -d --force-recreateHetzelfde geldt voor image-updates. Het ophalen van een nieuwe gluetun-image en het opnieuw aanmaken van alleen die service zorgt ervoor dat de andere containers verwijzen naar een namespace die niet langer bestaat.
FAQ
Waarom geeft Docker de melding "port publishing and the container type network mode"?
Omdat een ports:-blok nog aanwezig is op een service die ook network_mode: service:gluetun instelt. Het publiceren van een poort voegt een NAT-regel toe die een hostpoort doorstuurt naar de eigen netwerk-namespace van een container, en een container in deze modus heeft die niet. Verwijder het ports:-blok uit die service en voeg de identieke mapping toe aan de gluetun-service. Het poortnummer blijft gelijk, aangezien de applicatie nog steeds op die poort luistert binnen de gedeelde namespace.
Hoe bereiken andere containers een service die achter gluetun staat?
Containers binnen dezelfde namespace bereiken elkaar via 127.0.0.1. Containers daarbuiten gebruiken de servicenaam van gluetun, dus http://gluetun:8080 werkt waar http://qbittorrent:8080 dat niet doet. De applicatiecontainer heeft geen adres op een Docker-netwerk, dus de ingebouwde DNS-server heeft niets om voor die naam op te lossen. Hiervoor is geen poortpublicatie nodig, zolang beide containers een Compose-netwerk delen.
Wat moet ik invullen bij FIREWALL_OUTBOUND_SUBNETS?
Alleen de adressen waarmee een container achter gluetun een verbinding moet starten, zo specifiek mogelijk geschreven. Eén enkele machine is een /32. De twee meest voorkomende vermeldingen zijn de Docker-host op 172.17.0.1/32 en één /32 voor elke Tailscale-peer die u aanroept. Voeg nooit 0.0.0.0/0 toe en voeg nooit een bereik toe dat overlapt met de tunneladressen van uw eigen VPN. Inkomende verbindingen naar een gepubliceerde poort hebben hier geen vermelding nodig.
Waarom kan de container mijn Tailscale MagicDNS-namen niet omzetten?
MagicDNS werkt door de resolver van de host naar de DNS-server van Tailscale te laten wijzen, en de container gebruikt de resolver van de host niet. Deze gebruikt wat er in zijn eigen /etc/resolv.conf staat, wat achter gluetun de DNS-configuratie van gluetun is. Controleer dit met docker exec <container> cat /etc/resolv.conf. Gebruik het numerieke 100.x-adres van de peer, of koppel de naam vast met een extra_hosts-vermelding op die container.
Hoe bevestig ik dat het verkeer nog steeds via de VPN verloopt?
Voer één verzoek uit vanuit de namespace en hetzelfde verzoek vanaf de host, en vergelijk vervolgens de antwoorden. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org hoort het exit-adres van uw VPN-provider terug te geven, terwijl curl -s https://api.ipify.org op de VPS het VPS-adres teruggeeft. Twee overeenkomstige antwoorden betekenen dat de tunnel het verkeer van de container niet transporteert. Voer deze controle opnieuw uit na elke wijziging in FIREWALL_OUTBOUND_SUBNETS.