SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-28

Gluetun netwerk: containers bereikbaar maken

Een container achter Gluetun heeft geen eigen netwerkinterface. Leer hoe u poorten publiceert via Gluetun en specifieke subnets toestaat buiten de VPN tunnel voor optimale toegang.

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 niet langer eigenschappen van die container zijn, maar eigenschappen van de gluetun-service worden. 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}}' qbittorrent

Dit 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 mode

De reden 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 here

Een expose:-blok op de afhankelijke service is eveneens zinloos, en een networks:-blok daar 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 later merkbaar. Elke container in de namespace deelt één enkele poortruimte, dus twee applicaties die beide standaard op 8080 staan, botsen, en de tweede die start faalt met een 'address already in use'-fout. Wijzig een van beide in de eigen configuratie, bijvoorbeeld 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 de 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.conf

Hoe 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 onjuist 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 het systeem 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 geweigerde verbinding. Een weigering betekent dat het pakket is aangekomen en dat er een afwijzing is ontvangen. Een time-out betekent dat het pakket nooit is aangekomen.

  gluetun:
    environment:
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32

Controleer 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 deze 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 wijzigingen in de firewall en routering 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 blokkeert dat niet bestemd is voor de VPN-server.

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/32

Twee eigenschappen worden vaak 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.

Toegang tot de web-UI 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. Op een persoonlijk tailnet kost dit niets. De gebruikers- en apparaatlimieten van het gratis abonnement bepalen wel of dat zo blijft wanneer andere mensen toegang nodig hebben tot dezelfde interfaces. Omdat de facturering het aantal gebruikers telt en niet het aantal machines, hangt wat een betaald tailnet daadwerkelijk kost af van het aantal mensen dat u uitnodigt en niet van het aantal containers dat u voor hen beschikbaar stelt. Voor beide richtingen is ander werk nodig.

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 er daar een van. Een peer opent dus http://<machine-name>:8080 en bereikt de container. Gluetun speelt geen rol in dat pad, 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"

Zoek 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. Als u de UI liever bereikt via een HTTPS-naam dan via een host en poort, kan tailscale serve die gepubliceerde poort afschermen, hoewel serve en funnel verschillen in wie er uiteindelijk toegang toe heeft en slechts een van beide de UI binnen het tailnet houdt. Het omzeilt ook het probleem in Docker dat poorten direct voorbij ufw publiceert.

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. Als de machine die u aanroept zich op een privénetwerk bevindt dat wordt bereikt via een VPS die dat subnet adverteert aan uw tailnet, vermeld dan het geadverteerde bereik in plaats van het eigen 100.x-adres van de router, en bevestig dat de host zelf die routes heeft geaccepteerd. 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 leg het vast met een extra_hosts-regel. Hetzelfde geldt wanneer u uw eigen Tailscale-controleserver met Headscale draait.

Een compleet compose-bestand voor de gangbare opzet

Een downloadclient achter de VPN, twee web-interfaces die alleen reageren op het tailnet, 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-stopped

Bestudeer het bestand voor het patroon in plaats van de productnamen. Beide interfaces worden gepubliceerd op 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 afzonderlijke adressen: het Docker bridge-adres van de host, zodat Prowlarr een databaseverbinding kan openen, en één tailnet-peer.

De PostgreSQL-server ontbreekt bewust in 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 ernaast, het patroon dat wordt behandeld in env-bestanden en secrets voor Docker Compose. De condition: service_healthy-clausule gebruikt de healthcheck die de gluetun-image standaard meelevert, 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 het tailnet

Verwijder het adres-prefix en de poort-binds op 0.0.0.0, wat ook het publieke IP-adres van de VPS omvat. Doe dit alleen achter een firewall die u beheert, en lees eerst de ufw-opmerking 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.org

De eerste opdracht hoort het exit-adres van uw VPN-provider weer te geven. De tweede opdracht hoort het adres van de VPS weer te geven. Als deze overeenkomen, gaat het verkeer van de container niet door de tunnel en is elke oplossing in deze handleiding zinloos totdat dit is gecorrigeerd.

De routeringstabel toont wat wel en wat niet via de tunnel verloopt.

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

De standaardroute hoort naar de tunnelinterface, tun0, te wijzen. Daaronder hoort u per item in FIREWALL_OUTBOUND_SUBNETS één route te zien 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. Er zijn vier manieren om dit gat te groot te maken:

  • 0.0.0.0/0 stuurt al het verkeer buiten de tunnel om. De twee bovenstaande IP-controles 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/8 om één machine op 10.0.1.7 te bereiken, opent ook elk adres dat een torrent-peer in dat bereik zou kunnen adverteren. Gebruik 10.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/10 voor Tailscale. Dit opent grofweg vier miljoen adressen zodat één peer bereikbaar is. 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 na elke wijziging van deze variabele opnieuw de publieke IP-controle uit, 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 de levenscyclus 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 container

Het 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-recreate

Hetzelfde 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 meer bestaat.

FAQ

Waarom geeft Docker de melding "port publishing and the container type network mode"?

Dit komt doordat 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 draait?

Containers binnen dezelfde namespace bereiken elkaar via 127.0.0.1. Containers daarbuiten gebruiken de gluetun-servicenaam, waardoor 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 op te lossen voor die naam. 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 geformuleerd. 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 oplossen?

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 zet 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 zou het exit-adres van uw VPN-provider moeten retourneren, terwijl curl -s https://api.ipify.org op de VPS het adres van de VPS retourneert. Twee overeenkomende antwoorden betekenen dat de tunnel het verkeer van de container niet vervoert. Voer deze controle opnieuw uit na elke wijziging in FIREWALL_OUTBOUND_SUBNETS.