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

Docker container via VPN routeren met Gluetun

Wanneer u een container koppelt aan de netwerk-namespace van Gluetun verdwijnen gepubliceerde poorten. Leer waarom dit gebeurt en hoe u dit oplost in uw Docker Compose bestand.

Waarom poorten verdwijnen wanneer u Docker-containers via een VPN routeert

Om Docker-containers via een VPN te routeren, geeft u één container de tunnel en koppelt u de overige containers aan de netwerk-namespace van die container met network_mode: "service:gluetun". Die koppeling is het punt dat voor verrassingen zorgt. De gekoppelde container beschikt niet langer over een eigen netwerk, waardoor de gepubliceerde poorten en de Docker-servicenaam komen te vervallen. Publiceer de poorten in plaats daarvan op de VPN-container; andere containers bereiken de applicatie vervolgens via de naam van de VPN-container.

Laat u een ports:-blok staan op de gekoppelde container, dan weigert Docker deze volledig aan te maken:

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

De tool die hier wordt gebruikt is Gluetun, een container die verbinding maakt met een commerciële VPN-provider (virtual private network) via WireGuard of OpenVPN en over een eigen firewall beschikt. Release v3.41.3 is de huidige versie per augustus 2026. De voorbeelden maken gebruik van Mullvad met WireGuard; u heeft hiervoor een account en een sleutel van uw provider nodig. Als u de tunnel liever op eigen hardware beëindigt, bouwt uw eigen WireGuard-server op een VPS draaien de andere kant van de verbinding, en wg-easy in Docker verpakt dit in een webinterface.

Wat network_mode: "service:gluetun" daadwerkelijk doet

Elke Docker-container krijgt normaal gesproken een eigen netwerk-namespace: eigen interfaces, een eigen routeringstabel, firewallregels en listening sockets. De modus service: slaat die stap over en start de container binnen de namespace van gluetun. Eén namespace betekent één IP-adres, en dat verandert zes zaken.

  • De applicatie heeft geen eigen adres. Het adres is dat van gluetun.
  • De applicatie is niet verbonden met een Docker-netwerk, waardoor de servicenaam nooit wordt geregistreerd en nooit wordt omgezet. Andere containers moeten gluetun gebruiken.
  • Containers binnen de namespace bereiken elkaar via localhost.
  • Twee containers in dezelfde namespace kunnen niet op dezelfde poort luisteren. De documentatie van gluetun is hier duidelijk over: er is geen workaround.
  • Capabilities horen bij een container, niet bij een namespace. Gluetun bezit NET_ADMIN en /dev/net/tun omdat het de tunnelinterface aanmaakt. De gekoppelde container erft deze niet.
  • Compose weigert elk bestand waarin één service zowel network_mode als networks instelt. Koppel gluetun aan uw netwerken, en de applicatie lift mee.

Het herstarten van gluetun verbreekt de verbinding met alles wat eraan gekoppeld is. Dit is gedocumenteerd gedrag en is de reden waarom gluetun het VPN-proces binnen de container herstart in plaats van af te sluiten wanneer de verbinding wegvalt. Nadat u gluetun zelf herstart of opnieuw aanmaakt, moet u de containers die eraan gekoppeld zijn ook herstarten.

Het werkende compose-bestand

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-stopped

De :v3-tag is de nieuwste stabiele release in de v3-serie. De :latest-tag verwijst naar de laatste commit van de master-branch, wat de ontwikkelingsversie is. Pin daarom :v3 niet vast op een machine die u op een dinsdag niet wilt debuggen.

WEBUI_PORT=8080 moet overeenkomen met de gepubliceerde poort, omdat qBittorrent bindt binnen de namespace van gluetun en de publish-regel hostverkeer naar poort 8080 stuurt. Wijzig het ene getal zonder het andere en de poort reageert nergens op. 127.0.0.1:8080:8080 houdt de webinterface op het loopback-adres van de host. Een kale 8080:8080 publiceert op elke interface en schrijft zijn eigen firewall-regel; dit is hoe Docker gepubliceerde poorten direct langs ufw glippen.

Start de container en controleer deze vervolgens in deze volgorde:

docker compose up -d
docker compose ps
docker compose logs gluetun | tail -30

docker compose ps zou gluetun als healthy en qbittorrent als running moeten tonen. Bevestig daarna het exit-adres vanuit de namespace; dit is de controle die bepaalt of alles correct werkt:

docker run --rm --network=container:gluetun alpine:3.22 sh -c "apk add wget && wget -qO- https://ipinfo.io"

Het ip-veld in die JSON zou het adres van uw VPN-provider moeten zijn. Als dit het eigen adres van uw server is, bevindt de applicatie zich niet in de tunnel en zal niets hieronder zich gedragen zoals beschreven.

Houd de keys uit het compose-bestand

gluetun.env bevat de inloggegevens en deze blijven buiten git:

WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32

Beide waarden zijn afkomstig uit een WireGuard-configuratiebestand dat u genereert in de accountomgeving van uw provider. Zet het bestand op modus 600. Wees realistisch over wat dit oplevert: de key blijft buiten uw repository, maar docker inspect gluetun print nog steeds elke omgevingsvariabele naar iedereen die toegang heeft tot de Docker-socket. Omgevingsbestanden en secrets in Docker Compose behandelt de robuustere opties.

Hoe een container buiten de tunnel communiceert met een container erin

Beide richtingen werken en gebruiken elk een andere naam. De twee containers hebben een gedeeld Docker-netwerk nodig; dit is het netwerk van gluetun, aangezien de gekoppelde container zelf geen netwerk heeft. Hoe Docker Compose-netwerken zijn bedraad behandelt de standaardinstellingen.

Gebruik van buiten naar binnen de naam van gluetun en de poort waarop de applicatie luistert. Een reverse proxy-container bereikt de qBittorrent-webinterface via gluetun:8080. Hiervoor is geen ports:-vermelding nodig, omdat verkeer tussen containers op het Docker-netwerk blijft en nooit een hostpoort raakt.

Gebruik van binnen naar buiten de servicenaam van de andere container, bijvoorbeeld postgres:5432. Gluetun lost sinds v3.41 andere containernamen op vanuit zijn eigen namespace; gebruik dus die versie of nieuwer als een naam niet wordt omgezet.

De firewall van gluetun bepaalt wie een verbinding mag openen. Verkeer vanaf het eigen Docker-netwerk van gluetun is toegestaan. Een client op een ander subnet, een laptop op uw LAN of een container op een afzonderlijk bridge-netwerk wordt geweigerd totdat u dat subnet opgeeft:

FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

De gedocumenteerde betekenis is exact: door komma's gescheiden subnetten waartoe gluetun en de containers die de netwerkstack delen, toegang hebben.

Inkomende verbindingen vanaf het internet vormen een apart probleem. Peers van een torrent-client komen binnen via de VPN-zijde, dus het publiceren van poort 6881 op de host heeft voor hen geen effect. U heeft een doorgestuurde poort van uw provider nodig, en die poort moet worden vermeld in FIREWALL_VPN_INPUT_PORTS, wat poorten vanaf de VPN-serverzijde toestaat. Dit is het onderdeel dat de meeste mediastacks gebouwd met Docker Compose niet correct configureren.

De kill switch: wat gebeurt er als de tunnel wegvalt

Dit patroon bewijst zijn waarde bij storingen. De gekoppelde container heeft geen tweede route. Het enige pad naar buiten vanaf de machine is de namespace die hij deelt; als de tunnel wegvalt, is er geen alternatief. De firewall van Gluetun dwingt dezelfde regel af vanaf de andere kant: uitgaand verkeer gaat via de tunnel of naar het VPN-server-eindpunt, al het overige verkeer wordt geweigerd. Er is geen moment waarop pakketten via de standaard interface lekken terwijl een client opnieuw verbinding maakt.

Gluetun bewaakt zijn eigen verbinding. Elke minuut verstuurt het een ICMP echo (een ping) naar de adressen in HEALTH_ICMP_TARGET_IPS, die standaard op 1.1.1.1,8.8.8.8 staan. Elke vijf minuten voert het een volledige TCP- en TLS-dial (transport layer security) uit naar HEALTH_TARGET_ADDRESSES, standaard cloudflare.com:443,github.com:443. Wanneer deze falen, herstart het de VPN binnen de container en logt dit:

WARN [vpn] restarting VPN because it failed to pass the healthcheck: periodic check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout

Lees de logs van de gekoppelde container met deze volgorde in gedachten. Regels zoals connection refused, operation not permitted en i/o timeout binnen de applicatie zijn gevolgen van een weggevallen tunnel, geen oorzaken. De documentatie van Gluetun vermeldt dit expliciet, omdat gebruikers vaak het gevolg rapporteren en daar urenlang naar zoeken.

HEALTH_RESTART_VPN=on is de standaardinstelling en moet ingeschakeld blijven. Schakel deze alleen uit tijdens het debuggen van een specifieke fout, want zonder deze optie blijft een weggevallen tunnel permanent verbroken.

Volgorde: voorkom dat de stack start voordat de tunnel actief is

De image bevat een Docker healthcheck:

HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck

Dit commando voert een tweede, kortstondige kopie van gluetun uit die de health server van de actieve instantie bevraagt op http://127.0.0.1:9999/. Een werkende tunnel antwoordt met 200 OK. Een defecte tunnel antwoordt met 500 Internal server error inclusief een foutmelding, waarna de container na één enkele fout als ongezond wordt gemarkeerd.

condition: service_healthy is de instelling die hierop wacht. Een standaard depends_on: [gluetun] wacht enkel tot de container is gestart, wat enkele seconden gebeurt voordat de handshake is voltooid. Hierdoor start de applicatie in een dood netwerk en staakt deze vaak de poging bij de eerste verbindingspoging. Healthchecks in Docker Compose behandelt de syntaxis en de timing-velden.

Eén beperking zorgt vaak voor verwarring. Compose evalueert deze voorwaarde slechts één keer, op het moment dat de container wordt aangemaakt. Het stopt of herstart de applicatie niet als gluetun later ongezond wordt. De interne auto-healing van gluetun vangt dit scenario op; daarom herstart deze het VPN-proces in plaats van de container.

Controleer op een DNS-lek voordat u de configuratie vertrouwt

DNS (domain name system) is het lek dat blijft bestaan na een correcte tunnel. Gluetun draait een eigen resolver binnen de namespace en stuurt queries standaard door via DoT (DNS over TLS) naar Cloudflare: DNS_UPSTREAM_RESOLVER_TYPE=dot en DNS_UPSTREAM_RESOLVERS=cloudflare. Wijzig deze instellingen niet; uw opzoekingen zijn dan versleuteld en verlopen via de tunnel.

De instelling die dit proces verstoort is DNS_UPSTREAM_PLAIN_ADDRESSES. Gebruikers grijpen hiernaar wanneer een naam niet wordt omgezet en zij willen dat hun router of de resolver van hun provider antwoordt. De Gluetun-documentatie is hierover duidelijk: al het DNS-verkeer gaat dan niet door de VPN-tunnel en lekt naar buiten. Uw verkeer blijft privé, maar uw lijst met hostnames niet. De WireGuard-versie van deze fout wordt behandeld in DNS die stopt met omzetten via een WireGuard-tunnel.

Om dit te testen, stelt u HTTPPROXY=on in op gluetun en publiceert u 8888:8888/tcp. Wijs vervolgens een browser naar die proxy en voer een DNS-lektest uit. Het resultaat moet uw provider of Cloudflare tonen, nooit uw eigen router. De documentatie van Gluetun waarschuwt dat sommige lektests vreemde resultaten rapporteren, omdat de resolver binnen de namespace een lokale caching-tussenpersoon is in plaats van de server die uiteindelijk antwoordt. Beschouw een verkeerd land of de resolver van uw eigen ISP als het werkelijke signaal.

Tailscale toevoegen naast de VPN-sidecar, en welke krijgt voorrang

Tailscale is een overlay-netwerk gebaseerd op WireGuard om uw eigen machines te bereiken. Gebruikers draaien dit vaak naast een provider-VPN om een beheerpad naar de stack te behouden. De twee conflicteren zelden, en daar is een goede reden voor. De documentatie van Tailscale stelt de standaard: het fungeert als een overlay-netwerk, het routeert alleen verkeer tussen apparaten die Tailscale draaien en het raakt uw publieke internetverkeer niet aan.

Het antwoord hangt dus af van één instelling.

  • Tailscale in zijn eigen container, standaardconfiguratie: het ziet het uitgaande verkeer van de applicatie nooit. Gluetun verwerkt al het verkeer. Tailscale bereikt de applicatie op gluetun:8080, precies zoals elke andere externe container.
  • Tailscale gekoppeld aan de namespace van gluetun met network_mode: "service:gluetun": het heeft zijn eigen cap_add van net_admin en net_raw nodig, omdat capabilities niet automatisch meekomen met de namespace. In de standaard userspace-netwerkmodus staat TS_USERSPACE aan; tailscaled maakt geen interface aan en werkt als een SOCKS5- of HTTP-proxy, waardoor het de routering niet kan wijzigen. Gluetun verwerkt nog steeds alles.
  • Hetzelfde, met TS_USERSPACE=false: tailscaled maakt een tunnel-device aan en installeert routes, maar alleen voor het tailnet-bereik 100.64.0.0/10 plus eventuele subnet-routes die u adverteert met TS_ROUTES. Publiek verkeer verlaat het netwerk nog steeds via gluetun.
  • Elk van de bovenstaande opties met een geselecteerde exit node, sudo tailscale set --exit-node=<exit-node-ip>: Tailscale claimt de standaardroute en krijgt voorrang. Combineer dit niet met gluetun. Er is slechts één standaardroute en één eigenaar.

Als die geadverteerde routes het doel zijn en u wilt het hele privénetwerk achter de box bereikbaar maken in plaats van alleen de box zelf, dan behandelt het draaien van een Tailscale subnet router op een VPS de route-goedkeuring, de IP-forwarding en de client-side flag die TS_ROUTES op zichzelf niet afhandelt.

Als Tailscale aanwezig is om u een beheer-URL te geven in plaats van een route, dan plaatsen tailscale serve en tailscale funnel HTTPS voor gluetun:8080 voor uw tailnet, waarbij alleen funnel dit openstelt voor het publieke internet.

Een bijwerking is zichtbaar wanneer Tailscale binnen de tunnel draait. De peers zien het adres van de VPN-provider, dus verwacht dat het vaker terugvalt op relays. tailscale status toont relay "..." naast een peer in plaats van direct wanneer dit is gebeurd. De verbinding werkt, maar is trager. Als de overlay het enige is wat u daadwerkelijk nodig had, dan is het verschil tussen standaard WireGuard en Tailscale een beter startpunt.

Wat er misgaat en de melding die u zult zien

Docker weigert de app-container aan te maken. Error response from daemon: conflicting options: port publishing and the container type network mode betekent dat een ports:-blok nog steeds op de gekoppelde service staat. Verplaats dit naar gluetun.

Compose weigert het volledige bestand. Een service kan niet tegelijkertijd network_mode en networks instellen. Plaats de netwerken op gluetun.

Een andere container kan de app niet resolven. curl: (6) Could not resolve host: qbittorrent is correct gedrag, omdat de gekoppelde container geen netwerk heeft toegevoegd en geen naam heeft geregistreerd. Gebruik gluetun en de poort.

De tweede gekoppelde container start niet. Twee processen in dezelfde namespace kunnen niet dezelfde poort binden; de verliezende partij meldt dat het adres al in gebruik is. Wijzig de interne poort van de app of start een tweede gluetun.

De app heeft geen netwerk nadat u gluetun heeft aangepast. Het herstarten of opnieuw aanmaken van gluetun verbreekt de verbinding voor alle daaraan gekoppelde services. Herstart deze containers.

Kleine pagina's laden wel, maar grote pagina's blijven hangen. Dit is MTU (maximum transmission unit). De tunnel voegt overhead toe en een knooppunt in het pad verwijdert de te grote pakketten zonder een foutmelding terug te sturen. Verlaag WIREGUARD_MTU, probeer 1400 en daarna 1320.

Gluetun wordt nooit 'healthy'. De opstartcontrole benoemt de eerste verdachten: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. Controleer of de sleutel is verlopen, of de serverlijst verouderd is en of uw host-firewall uitgaand UDP-verkeer blokkeert.

FAQ

Waarom werken de gepubliceerde poorten van mijn container niet meer achter Gluetun?

Omdat network_mode: "service:gluetun" de container in de netwerk-namespace van Gluetun plaatst, en een namespace heeft slechts één IP-adres en één set luisterende poorten. De applicatie blijft luisteren, maar de publicatieregel moet worden toegepast op de container die de namespace beheert. Verplaats de ports:-lijst naar de gluetun-service. Als u deze op de gekoppelde service laat staan, zal Docker deze niet eens aanmaken: Error response from daemon: conflicting options: port publishing and the container type network mode.

Hoe bereik ik een container binnen de VPN-tunnel vanuit een container daarbuiten?

Gebruik de servicenaam van gluetun en de poort waarop de applicatie luistert, bijvoorbeeld gluetun:8080. De gekoppelde container bevindt zich niet op een eigen Docker-netwerk, waardoor de eigen naam nooit wordt omgezet. Voor verkeer tussen containers hoeft niets te worden gepubliceerd. Andersom bereikt een container binnen de namespace een externe container via de servicenaam, zoals postgres:5432, op Gluetun v3.41 en nieuwer. Een client op een ander subnet, zoals een laptop op uw LAN, wordt door de firewall van gluetun geblokkeerd totdat u dat subnet toevoegt aan FIREWALL_OUTBOUND_SUBNETS.

Werkt Gluetun als een kill switch wanneer de VPN wegvalt?

Ja, en wel om twee redenen tegelijk. De gekoppelde container heeft geen andere route dan die in de gedeelde namespace, dus een verbroken tunnel laat de container achter zonder pad naar buiten. De firewall van Gluetun staat bovendien alleen uitgaand verkeer toe via de tunnel en naar het eindpunt van de VPN-server. Gluetun herstart de VPN vervolgens intern, waarbij WARN [vpn] restarting VPN because it failed to pass the healthcheck wordt gelogd, in plaats van af te sluiten, omdat elke gekoppelde container zijn netwerk verliest wanneer gluetun zelf herstart.

Tailscale en Gluetun in dezelfde stack: welke verzorgt het uitgaande verkeer?

Gluetun, in elke configuratie behalve één. Tailscale routeert standaard alleen verkeer tussen apparaten in uw tailnet en laat openbaar verkeer ongemoeid. In de standaard userspace-modus van de container-image wordt er helemaal geen interface aangemaakt, dus kan het de routering niet beïnvloeden. Met TS_USERSPACE=false installeert het alleen routes voor 100.64.0.0/10 en uw geadverteerde subnetten. De uitzondering is een exit node: sudo tailscale set --exit-node=<exit-node-ip> maakt van Tailscale de standaardroute, en dan wint Tailscale. Kies één product om de standaardroute te beheren in plaats van beide te stapelen.