Docker-Container über VPN routen: Ports richtig freigeben
Bei Gluetun verschwinden Ports und Servicenamen im gemeinsamen Netzwerk-Namespace. Erfahren Sie, warum das passiert und wie die passende Compose-Datei aussieht.
Warum die Ports verschwinden, wenn Sie Docker-Container über ein VPN routen
Um Docker-Container über ein VPN zu routen, geben Sie einem Container den Tunnel und hängen die anderen mit network_mode: "service:gluetun" an dessen Netzwerk-Namespace an. Diese Anbindung sorgt häufig für Verwirrung. Der angebundene Container hat dann kein eigenes Netzwerk mehr. Seine veröffentlichten Ports und sein Docker-Dienstname entfallen dadurch ebenfalls. Veröffentlichen Sie die Ports stattdessen am VPN-Container. Andere Container erreichen die Anwendung dann über den Namen des VPN-Containers.
Wenn Sie am angebundenen Container einen ports:-Block belassen, verweigert Docker dessen Erstellung vollständig:
Error response from daemon: conflicting options: port publishing and the container type network modeDas verwendete Tool ist Gluetun. Dabei handelt es sich um einen Container, der über WireGuard oder OpenVPN eine Verbindung zu einem kommerziellen VPN-Anbieter (virtuelles privates Netzwerk) herstellt und eine eigene Firewall mitbringt. Die aktuelle Version ist v3.41.3 (Stand: August 2026). Die Beispiele verwenden Mullvad mit WireGuard. Sie benötigen daher ein Konto und einen Schlüssel von Ihrem Anbieter. Wenn Sie den Tunnel lieber auf eigener Hardware terminieren möchten, richtet Ihren eigenen WireGuard-Server auf einem VPS ein und wg-easy in Docker stellt dafür eine Weboberfläche bereit.
Was network_mode: "service:gluetun" tatsächlich bewirkt
Jeder Docker-Container erhält normalerweise einen eigenen Netzwerk-Namespace mit eigenen Schnittstellen, einer eigenen Routing-Tabelle, eigenen Firewall-Regeln und eigenen Listening-Sockets. Der Modus service: überspringt diesen Schritt und startet den Container im Namespace von gluetun. Ein Namespace bedeutet eine IP-Adresse. Dadurch ändern sich sechs Dinge.
- Die Anwendung hat keine eigene Adresse. Ihre Adresse ist die Adresse von gluetun.
- Die Anwendung ist mit keinem Docker-Netzwerk verbunden. Daher wird ihr Dienstname weder registriert noch aufgelöst. Andere Container müssen
gluetunverwenden. - Container innerhalb des Namespace erreichen sich über
localhost. - Zwei Container in einem Namespace können nicht denselben Port verwenden. Die Gluetun-Dokumentation formuliert es eindeutig: Es gibt dafür keine Umgehung.
- Capabilities gehören zu einem Container, nicht zu einem Namespace. Gluetun besitzt
NET_ADMINund/dev/net/tun, weil es die Tunnel-Schnittstelle erstellt. Der verbundene Container erbt diese Capabilities nicht. - Compose lehnt jede Datei ab, in der ein Dienst sowohl
network_modeals auchnetworkssetzt. Verbinden Sie gluetun mit Ihren Netzwerken. Die Anwendung verwendet diese Verbindung dann ebenfalls.
Ein Neustart von gluetun trennt alle daran verbundenen Container. Dieses Verhalten ist dokumentiert. Deshalb startet gluetun den VPN-Prozess innerhalb des Containers neu, anstatt den Container zu beenden, wenn die Verbindung fehlschlägt. Nachdem Sie gluetun selbst neu gestartet oder neu erstellt haben, starten Sie die daran verbundenen Container ebenfalls neu.
Die Compose-Datei, die funktioniert
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-stoppedDer Tag :v3 ist das neueste stabile Release der v3-Serie. Der Tag :latest verweist auf den letzten Commit des master-Branches, also auf den Entwicklungsstand. Verwenden Sie :v3 daher auf einem Rechner, den Sie nicht an einem Dienstag debuggen möchten.
WEBUI_PORT=8080 muss mit dem veröffentlichten Port übereinstimmen, weil qBittorrent innerhalb des Netzwerk-Namespace von gluetun bindet und die Publish-Regel den Host-Verkehr dort an Port 8080 weiterleitet. Wenn Sie eine der beiden Zahlen ändern, ohne die andere anzupassen, antwortet der Port nicht. 127.0.0.1:8080:8080 hält die Weboberfläche an die Loopback-Adresse des Hosts gebunden. Ein alleinstehendes 8080:8080 veröffentlicht den Port auf jeder Schnittstelle und erstellt eine eigene Firewall-Regel. Dadurch können veröffentlichte Docker-Ports direkt an ufw vorbeigehen.
Starten Sie den Stack und prüfen Sie ihn anschließend in dieser Reihenfolge:
docker compose up -d
docker compose ps
docker compose logs gluetun | tail -30docker compose ps sollte gluetun als healthy und qbittorrent als running anzeigen. Bestätigen Sie anschließend die Exit-Adresse innerhalb des Namespace. Diese Prüfung entscheidet über alles Weitere:
docker run --rm --network=container:gluetun alpine:3.22 sh -c "apk add wget && wget -qO- https://ipinfo.io"Das Feld ip in diesem JSON sollte die Adresse Ihres VPN-Anbieters enthalten. Wenn dort die eigene Adresse Ihres Servers steht, befindet sich die Anwendung nicht im Tunnel. Dann wird nichts weiter wie beschrieben funktionieren.
Schlüssel aus der Compose-Datei heraushalten
gluetun.env enthält die Zugangsdaten und wird nicht in Git eingecheckt:
WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32Beide Werte stammen aus einer WireGuard-Konfigurationsdatei, die Sie im Kontobereich Ihres Providers erzeugen. Setzen Sie die Datei auf den Modus 600. Machen Sie sich den Schutzumfang klar: Der Schlüssel bleibt aus Ihrem Repository heraus, aber docker inspect gluetun gibt weiterhin jede Umgebungsvariable für alle aus, die den Docker-Socket erreichen können. Umgebungsdateien und Secrets in Docker Compose behandelt die besseren Optionen.
Wie ein Container außerhalb des Tunnels mit einem Container darin kommuniziert
Die Kommunikation funktioniert in beide Richtungen. Für jede Richtung wird ein anderer Name verwendet. Die beiden Container benötigen ein gemeinsames Docker-Netzwerk. Das ist das Netzwerk von gluetun, da der eingebundene Container kein eigenes Netzwerk besitzt. Aufbau von Docker-Compose-Netzwerken beschreibt die Standardeinstellungen.
Für die Kommunikation von außen nach innen verwenden Sie den Namen von gluetun und den Port, auf dem die Anwendung lauscht. Ein Reverse-Proxy-Container erreicht die Weboberfläche von qBittorrent unter gluetun:8080. Dafür ist kein Eintrag ports: erforderlich, da der Datenverkehr zwischen Containern im Docker-Netzwerk bleibt und keinen Host-Port verwendet.
Für die Kommunikation von innen nach außen verwenden Sie beispielsweise den Servicenamen des anderen Containers, postgres:5432. Gluetun kann andere Containernamen seit v3.41 aus seinem Namespace auflösen. Verwenden Sie daher diese oder eine neuere Version, wenn ein Name nicht aufgelöst werden kann.
Die Firewall von Gluetun entscheidet, wer eine Verbindung zu Gluetun öffnen darf. Datenverkehr aus dem eigenen Docker-Netzwerk von gluetun ist erlaubt. Ein Client aus einem anderen Subnetz, ein Laptop in Ihrem LAN oder ein Container in einem separaten Bridge-Netzwerk wird verworfen, bis Sie dieses Subnetz angeben:
FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24Die dokumentierte Bedeutung ist eindeutig: durch Kommas getrennte Subnetze, auf die Gluetun und die Container mit gemeinsamem Netzwerk-Stack zugreifen dürfen.
Eingehende Verbindungen aus dem Internet sind ein separates Problem. Die Peers eines Torrent-Clients kommen über die VPN-Seite. Das Veröffentlichen von Port 6881 auf dem Host hat daher keine Wirkung. Sie benötigen einen weitergeleiteten Port von Ihrem Provider und müssen diesen Port in FIREWALL_VPN_INPUT_PORTS angeben. Dadurch werden Ports von der Seite des VPN-Servers zugelassen. Dieser Teil bleibt in den meisten mit Docker Compose erstellten Media-Stacks fehlerhaft.
Der Kill Switch: Was passiert, wenn der Tunnel ausfällt
Dieses Muster rechtfertigt seine Komplexität im Fehlerfall. Der angehängte Container hat keine zweite Route. Sein einziger Weg vom Rechner führt über den Namespace, den er gemeinsam nutzt. Wenn der Tunnel ausfällt, gibt es daher keinen Ausweichweg. Die Firewall von Gluetun erzwingt dieselbe Regel von der anderen Seite: Ausgehender Datenverkehr läuft entweder über den Tunnel oder zum Endpunkt des VPN-Servers. Alles andere wird verworfen. Es gibt kein Zeitfenster, in dem Pakete während der erneuten Verbindung eines Clients über das normale Interface nach außen gelangen.
Gluetun überwacht seine eigene Verbindung. Jede Minute sendet es ein ICMP Echo (einen Ping) an die Adressen in HEALTH_ICMP_TARGET_IPS, deren Standardwert 1.1.1.1,8.8.8.8 ist. Alle fünf Minuten baut es eine vollständige TCP- und TLS-Verbindung (Transport Layer Security) zu HEALTH_TARGET_ADDRESSES auf. Der Standardwert ist cloudflare.com:443,github.com:443. Wenn diese Prüfungen fehlschlagen, startet es das VPN im Container neu und protokolliert den Vorgang:
WARN [vpn] restarting VPN because it failed to pass the healthcheck: periodic check: dialing: dial tcp4: lookup cloudflare.com: i/o timeoutLesen Sie die Logs des angehängten Containers unter diesem Gesichtspunkt. Zeilen wie connection refused, operation not permitted und i/o timeout innerhalb der Anwendung sind Folgen eines ausgefallenen Tunnels und keine Ursachen. In der Gluetun-Dokumentation wird dies ausdrücklich erklärt, weil viele Benutzer die Folge melden und stundenlang nach der Ursache suchen.
HEALTH_RESTART_VPN=on ist der Standardwert und sollte aktiviert bleiben. Deaktivieren Sie die Option nur, wenn Sie gerade einen bestimmten Fehler untersuchen. Bei deaktivierter Option bleibt ein ausgefallener Tunnel dauerhaft ausgefallen.
Reihenfolge: Verhindern, dass der Stack startet, bevor der Tunnel aktiv ist
Das Image enthält einen Docker-Healthcheck:
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheckDieser Befehl startet eine zweite, kurzlebige Instanz von gluetun. Sie fragt den Health-Server der laufenden Instanz unter http://127.0.0.1:9999/ ab. Ein funktionierender Tunnel antwortet mit 200 OK. Ein fehlerhafter Tunnel antwortet mit 500 Internal server error und einer Fehlermeldung. Nach einem einzigen Fehlschlag wird der Container als nicht gesund markiert.
Auf condition: service_healthy wird gewartet. Das einfache depends_on: [gluetun] wartet nur, bis der Container gestartet ist. Das geschieht mehrere Sekunden, bevor der Handshake abgeschlossen ist. Daher startet die Anwendung in einem nicht funktionsfähigen Netzwerk und gibt den ersten Verbindungsversuch häufig sofort auf. Healthchecks in Docker Compose erläutert die Syntax und die Zeitangaben.
Eine Einschränkung führt häufig zu Problemen. Compose wertet diese Bedingung einmal aus, wenn der Container erstellt wird. Wenn gluetun später als nicht gesund markiert wird, stoppt oder startet Compose die Anwendung nicht neu. Die interne automatische Fehlerbehebung von gluetun übernimmt diesen Fall. Deshalb startet sie den VPN-Prozess statt des Containers neu.
Prüfen Sie auf ein DNS-Leak, bevor Sie der Konfiguration vertrauen
DNS (Domain Name System) ist das Leak, das trotz eines korrekt eingerichteten Tunnels bestehen bleibt. Gluetun betreibt seinen eigenen Resolver innerhalb des Namespace und leitet Abfragen standardmäßig über DoT (DNS over TLS) an Cloudflare weiter: DNS_UPSTREAM_RESOLVER_TYPE=dot und DNS_UPSTREAM_RESOLVERS=cloudflare. Lassen Sie beide Einstellungen unverändert. Dann werden Ihre DNS-Abfragen verschlüsselt und durch den Tunnel übertragen.
Die Einstellung, die dieses Verhalten außer Kraft setzt, ist DNS_UPSTREAM_PLAIN_ADDRESSES. Sie wird häufig aktiviert, wenn ein Name nicht aufgelöst werden kann und stattdessen der Router oder der Resolver des Providers antworten soll. Die Gluetun-Dokumentation beschreibt die Folge eindeutig: Der gesamte DNS-Verkehr läuft dann nicht durch den VPN-Tunnel, sondern tritt außerhalb des Tunnels aus. Ihr Datenverkehr bleibt privat. Die Liste Ihrer Hostnamen jedoch nicht. Die entsprechende Fehlkonfiguration bei WireGuard wird unter DNS-Auflösung, die über einen WireGuard-Tunnel ausfällt beschrieben.
Setzen Sie für den Test HTTPPROXY=on auf gluetun und veröffentlichen Sie 8888:8888/tcp. Richten Sie anschließend einen Browser auf diesen Proxy und laden Sie einen DNS-Leak-Test. Das Ergebnis sollte Ihren Provider oder Cloudflare nennen, niemals Ihren Heimrouter. Die Gluetun-Dokumentation weist darauf hin, dass manche Leak-Tests ungewöhnliche Ergebnisse liefern, weil der Resolver innerhalb des Namespace ein lokaler zwischenspeichernder Vermittler ist und nicht der Server, der letztlich antwortet. Ein falsches Land oder der Resolver Ihres eigenen Internetproviders ist daher das relevante Signal.
Tailscale neben dem VPN-Sidecar einrichten und welches Routing gewinnt
Tailscale ist ein auf WireGuard basierendes Overlay-Netzwerk für den Zugriff auf eigene Geräte. Viele betreiben es neben einem Provider-VPN, um einen administrativen Zugang zur Umgebung zu behalten. Die beiden Komponenten kommen nur selten miteinander in Konflikt. Der Grund dafür ist wichtig. Die Dokumentation von Tailscale beschreibt das Standardverhalten: Tailscale arbeitet als Overlay-Netzwerk, routet nur Datenverkehr zwischen Geräten mit aktivem Tailscale und verändert den öffentlichen Internetverkehr nicht.
Die Antwort hängt daher von einer Einstellung ab.
- Tailscale läuft in einem eigenen Container mit der Standardkonfiguration: Es sieht den ausgehenden Datenverkehr der Anwendung nie. Gluetun übernimmt den gesamten Datenverkehr. Tailscale erreicht die Anwendung unter
gluetun:8080, genau wie jeder andere externe Container. - Tailscale ist mit dem Namespace von gluetun verbunden und verwendet
network_mode: "service:gluetun": Es benötigt ein eigenescap_addvonnet_adminundnet_raw, weil Berechtigungen nicht automatisch mit dem Namespace übernommen werden. Im standardmäßigen Userspace-Networking-Modus istTS_USERSPACEaktiviert. tailscaled erstellt dann überhaupt kein Interface und arbeitet als SOCKS5- oder HTTP-Proxy. Daher kann es das Routing nicht ändern. Gluetun übernimmt weiterhin den gesamten Datenverkehr. - Dasselbe gilt mit
TS_USERSPACE=false: tailscaled erstellt ein Tunnelgerät und installiert Routen, aber nur für den Tailnet-Bereich100.64.0.0/10sowie für Subnetzrouten, die Sie mitTS_ROUTESbekannt geben. Der öffentliche Datenverkehr wird weiterhin über gluetun geleitet. - Bei jeder der genannten Varianten mit ausgewähltem Exit Node,
sudo tailscale set --exit-node=<exit-node-ip>: Tailscale übernimmt die Standardroute und gewinnt. Kombinieren Sie dies nicht mit gluetun. Es darf nur eine Standardroute und nur einen Besitzer geben.
Ein Nebeneffekt wird sichtbar, wenn Tailscale innerhalb des Tunnels läuft. Die Peers sehen dann die Adresse des VPN-Providers. Daher verwendet Tailscale häufiger Relays. tailscale status zeigt neben einem Peer relay "..." statt direct an, wenn dies geschehen ist. Die Verbindung funktioniert, ist aber langsamer. Wenn Sie tatsächlich nur das Overlay benötigen, ist der Unterschied zwischen einfachem WireGuard und Tailscale der bessere Ausgangspunkt.
Was ausfällt und welche Meldung Sie sehen
Docker weigert sich, den Anwendungscontainer zu erstellen. Error response from daemon: conflicting options: port publishing and the container type network mode bedeutet, dass auf dem verbundenen Dienst noch ein ports:-Block vorhanden ist. Verschieben Sie ihn zu gluetun.
Compose weist die gesamte Datei zurück. Ein Dienst darf nicht gleichzeitig network_mode und networks setzen. Tragen Sie die Netzwerke bei gluetun ein.
Ein anderer Container kann die Anwendung nicht auflösen. curl: (6) Could not resolve host: qbittorrent ist das erwartete Verhalten, weil der verbundene Container keinem Netzwerk beigetreten ist und keinen Namen registriert hat. Verwenden Sie gluetun und den Port.
Der zweite verbundene Container startet nicht. Zwei Prozesse in einem Namespace können nicht denselben Port binden. Der unterlegene Prozess meldet, dass die Adresse bereits verwendet wird. Ändern Sie den internen Port der Anwendung oder starten Sie ein zweites gluetun.
Die Anwendung hat kein Netzwerk mehr, nachdem Sie gluetun geändert haben. Beim Neustart oder bei der Neuerstellung von gluetun wird die Verbindung für alle daran verbundenen Container unterbrochen. Starten Sie diese Container neu.
Kleine Seiten werden geladen, große hängen. Das ist ein MTU-Problem (maximum transmission unit). Der Tunnel fügt zusätzlichen Overhead hinzu. Ein Gerät im Übertragungsweg verwirft die zu großen Pakete, ohne einen Fehler zurückzumelden. Verringern Sie WIREGUARD_MTU, testen Sie 1400 und verwenden Sie anschließend 1320.
Gluetun wird nie als fehlerfrei gemeldet. Die Startprüfung nennt die ersten Verdächtigen: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. Prüfen Sie, ob der Schlüssel abgelaufen ist. Prüfen Sie danach, ob die Serverliste veraltet ist und ob die Firewall auf dem Host ausgehendes UDP blockiert.
FAQ
Warum funktionieren die veröffentlichten Ports meines Containers hinter Gluetun nicht mehr?
Weil network_mode: "service:gluetun" den Container in den Netzwerk-Namespace von gluetun verschiebt. Ein Namespace hat eine IP-Adresse und einen Satz von Ports, auf denen Verbindungen angenommen werden. Die Anwendung lauscht weiterhin, aber die Portfreigabe muss am Container definiert sein, der den Namespace besitzt. Verschieben Sie die ports:-Liste in den gluetun-Dienst. Wenn sie am verbundenen Dienst verbleibt, erstellt Docker diesen nicht einmal: Error response from daemon: conflicting options: port publishing and the container type network mode.
Wie erreiche ich einen Container innerhalb des VPN-Tunnels von einem Container außerhalb des Tunnels?
Verwenden Sie den Dienstnamen von gluetun und den Port, auf dem die Anwendung lauscht, zum Beispiel gluetun:8080. Der verbundene Container verfügt über kein eigenes Docker-Netzwerk. Daher wird sein eigener Name niemals aufgelöst. Für Datenverkehr zwischen Containern muss kein Port veröffentlicht werden. In die andere Richtung erreicht ein Container innerhalb des Namespace einen externen Container über dessen Dienstnamen, beispielsweise postgres:5432, bei Gluetun v3.41 und neuer. Ein Client in einem anderen Subnetz, etwa ein Laptop in Ihrem LAN, wird von der gluetun-Firewall verworfen, bis Sie dieses Subnetz zu FIREWALL_OUTBOUND_SUBNETS hinzufügen.
Funktioniert Gluetun bei einem Abbruch der VPN-Verbindung als Kill Switch?
Ja, und zwar aus zwei Gründen gleichzeitig. Der verbundene Container verfügt über keine Route außer der Route im gemeinsam genutzten Namespace. Wenn der Tunnel ausfällt, hat der Container daher keinen Pfad aus dem System heraus. Die gluetun-Firewall erlaubt ausgehenden Datenverkehr außerdem nur über den Tunnel und zum Endpunkt des VPN-Servers. Gluetun startet das VPN intern neu und protokolliert dabei WARN [vpn] restarting VPN because it failed to pass the healthcheck, statt den Prozess zu beenden. Andernfalls würde jeder verbundene Container sein Netzwerk verlieren, sobald gluetun selbst neu startet.
Tailscale und Gluetun im selben Stack: Welches Programm überträgt ausgehenden Datenverkehr?
Gluetun, außer in einer Konfiguration. Tailscale leitet standardmäßig nur Datenverkehr zwischen Geräten in Ihrem tailnet weiter und lässt öffentlichen Datenverkehr unverändert. Im standardmäßigen Userspace-Modus des Container-Images erstellt Tailscale überhaupt kein Interface und kann daher das Routing nicht beeinflussen. Mit TS_USERSPACE=false installiert Tailscale nur Routen für 100.64.0.0/10 und Ihre angekündigten Subnetze. Die Ausnahme ist ein Exit Node: sudo tailscale set --exit-node=<exit-node-ip> macht Tailscale zur Standardroute, wodurch Tailscale Vorrang erhält. Legen Sie fest, welches der beiden Produkte die Standardroute verwaltet, statt beide zu kombinieren.