Je tunnelcontainer werkt, docker compose exec vpn curl -s https://ifconfig.me antwoordt met het adres van de provider, en toch verlaat de browser op je desktop je ISP. Die kloof is te verwachten. Een container die een VPN-client draait, houdt een netwerknaamruimte, routes en een tunnelinterface, en niets dat HTTP of SOCKS spreekt; een poort erop publiceren kan geen listener maken die er nooit was.

Waar de client draait, bepaalt het ontwerp. Een zustercontainer kan de naamruimte van de tunnel binnen, een desktopapplicatie niet, en een router heeft routes nodig in plaats van een proxyveld. Doe je de mapping verkeerd, dan is het symptoom hetzelfde: verbinding geweigerd, of verkeer dat stilletjes de oude weg neemt.

Stem de opstelling af op de plek waar de client draait

Stel één vraag voordat je Compose bewerkt: kan de client binnen de netwerknaamruimte van de VPN-container starten? Alles wat al als Linux-container draait, kan dat. Een browser, een IDE of een CLI op je bureau kan dat niet, omdat de proxyinstelling een protocoldienst verwacht, geen naamruimte.

ClientArrangementWhat must already exist
Another Compose serviceShares the vpn namespace via network_mode: service:vpnA working tunnel and correct start order
Host browser, IDE or CLIProxy listener inside the namespace, published to host loopbackA configured SOCKS or HTTP proxy and its access rules
Router, VM or phoneThe container as a layer-3 gatewayForwarding, NAT and a return route

Deze drie mengen niet. Een HTTP-proxyveld dat naar het bridgeadres van de container wijst, bereikt niets tenzij daar een proxy luistert, en een proxylistener routeert het verkeer van een router niet. Beslis eerst en volg dan het bijbehorende deel.

Deel de tunnelnaamruimte met een zustercontainer

Dit is de goedkoopste optie omdat er geen proxyprotocol aan te pas komt. De app stapt in de naamruimte van de vpn-dienst en erft de interfaces, routes en poortruimte:

services:
  vpn:
    image: your-vpn-client
    cap_add:
      - NET_ADMIN
    healthcheck:
      test: ["CMD-SHELL", "ip link show tun0 >/dev/null"]
      interval: 10s
      retries: 5
  app:
    image: your-app
    network_mode: service:vpn
    depends_on:
      vpn:
        condition: service_healthy

Drie beperkingen volgen uit het feit dat de naamruimte bij een andere dienst hoort. De app krijgt geen eigen networks-sleutel, omdat hij geen naamruimte heeft om aan te hangen. Poortpublicatie hoort bij vpn, want daar woont de naamruimte. En het opnieuw aanmaken van vpn vernietigt de naamruimte waar de app bij hoorde, dus docker compose up -d --force-recreate vpn laat jou ook app opnieuw aanmaken; een gewone herstart is niet altijd genoeg. De Compose-dienstreferentie behandelt deze opstelling en de beperking voor poortpublicatie.

Ga er niet van uit dat de resolver meekomt. /etc/resolv.conf is een bestand per container, geen deel van de netwerknaamruimte, dus de app kan nog steeds de resolver van de host vragen terwijl zijn pakketten door de tunnel naar buiten gaan. Lees het in de container en stel dns: in op de app wanneer privénamen via de tunnel moeten worden opgelost.

docker compose exec app ip route
docker compose exec app cat /etc/resolv.conf
docker compose exec app curl -s https://ifconfig.me

Bevat de routelijst de tunnelinterface en is het adres van de provider, dan zit de app in de tunnel. Ontbreekt de interface, dan is app gestart voordat vpn een tunnel had, dus maak app opnieuw aan zodra vpn gezond meldt.

Plaats een echte proxylistener in de naamruimte voor hostclients

Een desktopbrowser kan geen lid worden van een naamruimte, dus de tunnel moet als dienst worden blootgesteld. Draai een echte SOCKS- of HTTP-proxy in de vpn-naamruimte en publiceer alleen de poort:

services:
  vpn:
    ports:
      - "127.0.0.1:1080:1080"
  proxy:
    image: your-proxy
    network_mode: service:vpn
    command: ["--socks", "0.0.0.0:1080"]

Het adres aan de hostkant is de belangrijke helft van die mapping. 127.0.0.1:1080 links houdt de listener buiten je LAN; een kale "1080:1080" publiceert een open proxy aan elk apparaat dat je machine kan bereiken.

De containerkant heeft zijn eigen valkuil. Gepubliceerde poorten worden afgeleverd op het adres van de container op de bridge, niet op zijn loopback, dus een proxy die met --socks 127.0.0.1:1080 in de naamruimte is gestart, accepteert verbindingen van zusters en weigert alles wat via de gepubliceerde poort binnenkomt. Bind hem aan 0.0.0.0 en vertrouw voor isolatie op de loopbackbinding aan de hostkant. Dezelfde regel geldt voor het luisteradres in een proxyconfiguratiebestand.

Bepaal daarna waar namen worden opgelost. In curl lost --socks5 de hostnaam lokaal op en --socks5-hostname stuurt de naam naar de proxy; browsers bieden dezelfde schakelaar onder een ander label. Lokale resolutie lekt de query, zelfs wanneer de pagina via de tunnel laadt.

Behandel de container alleen met een terugroute als gateway

Routers, VM's en telefoons zullen geen HTTP-proxyveld gebruiken, en sommige applicaties negeren proxyvariabelen volledig. Hun verkeer routeren betekent laag 3: de container stuurt pakketten door en vertaalt adressen op de tunnelinterface.

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

Twee dingen worden makkelijk gemist. De client heeft een route nodig die zijn standaardgateway naar deze host wijst, en antwoorden moeten de weg terug vinden, hetzij via een statische route op de upstream-router, hetzij via NAT op de interface richting de clients. Zonder een van beide draagt de tunnel het verzoek naar buiten en verdwijnt het antwoord.

Hij heeft ook de breedste impact. Een host die als gateway voor het hele LAN dient, stuurt elke verkeerd geconfigureerde client door je VPN-account, en een tunneldip blokkeert het LAN of valt terug op de ISP, afhankelijk van de kill switch.

Verifieer het uitgangspad en rol daarna in volgorde terug

Controleer in deze volgorde en stop bij de eerste fout: uitgangsadres in de naamruimte, uitgangsadres vanaf de host via de proxy, dan naamresolutie voor een privéhost.

docker compose exec vpn curl -s https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 http://internal.example/health

De eerste twee adressen moeten overeenkomen met de provider, en het derde moet via de proxy worden opgelost in plaats van te falen met een naamfout. Een overeenkomende eerste regel en een falende tweede regel betekenen dat de listener of de gepubliceerde poort fout is, niet de tunnel.

Terugrollen is het omgekeerde van de opzet. Verwijder de proxy-omgevingsvariabelen uit het shellprofiel en uit elke systemd-unit, en breng de stack dan naar beneden zodat de gedeelde naamruimte met zijn eigenaar verdwijnt. Verwijder de gepubliceerde poort voordat je de proxydienst verwijdert, anders houdt de host een loopbacklistener die niemand leest.

Voordat je verdergaat, bevestig vier dingen: de client die je wichtig vindt, bereikt echt de tunnel, alleen de eigenaar van de naamruimte publiceert poorten, privénamen worden opgelost waar je dat bedoelde, en niets luistert op een hostadres dat geen loopback is. Elk daarvan faalt stil in productie, en elk is een controle van één regel hierboven.

Gerelateerde artikelen