Gluetun: Host und andere Container erreichen
Ein Container hinter gluetun hat keine eigenen Schnittstellen. Veröffentlichen Sie Ports auf gluetun und erlauben Sie nur die Subnetze, die außerhalb des Tunnels nötig sind.
Was passiert, wenn ein Container dem Netzwerk von gluetun beitritt
Ein Container, der network_mode: service:gluetun setzt, besitzt keine eigenen Netzwerkschnittstellen. Er tritt dem Netzwerk-Namespace von gluetun bei. Dadurch sind Portfreigaben und Firewall-Regeln keine Eigenschaften dieses Containers mehr, sondern Eigenschaften des Dienstes gluetun. Jede folgende Antwort ergibt sich aus dieser einen Tatsache.
Ein Netzwerk-Namespace ist eine private Kopie des Netzwerk-Stacks im Kernel. Er verfügt über eigene Schnittstellen, eine eigene Routing-Tabelle, eigene Firewall-Regeln und eigene Listening-Sockets. Docker weist standardmäßig jedem Container einen solchen Namespace zu. Wenn Sie network_mode: service:gluetun schreiben, überspringt Docker diesen Schritt und startet den neuen Container in dem Namespace, den gluetun bereits besitzt. Der Container behält sein eigenes Dateisystem und seine eigene /etc/hosts-Datei. Letztere ist später relevant.
Sie können dies direkt anzeigen.
docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrentDie Ausgabe enthält container:, gefolgt von der Container-ID von gluetun. Bei einem normalen Container würde stattdessen bridge ausgegeben. Dieser Leitfaden setzt dort fort, wo Docker-Verkehr mit gluetun über ein VPN leiten endet: Der Tunnel funktioniert, und nun kann nichts mit dem Container kommunizieren.
Port auf gluetun veröffentlichen, nicht auf der Anwendung
Lassen Sie einen ports:-Block beim Dienst stehen, der network_mode setzt, verweigert Docker die Erstellung des Containers:
Error response from daemon: conflicting options: port publishing and the container type network modeDer Grund ist direkt. Beim Veröffentlichen eines Ports wird eine NAT-Regel (Network Address Translation) hinzugefügt. Sie leitet einen Host-Port in den Netzwerk-Namespace des Containers weiter. Dieser Container besitzt jedoch keinen eigenen Netzwerk-Namespace. Verschieben Sie das Mapping zum Dienst gluetun. Die Portnummer ändert sich nicht, weil die Anwendung weiterhin auf diesem Port im gemeinsamen Namespace lauscht.
services:
gluetun:
ports:
- "8080:8080/tcp" # qBittorrent web UI
qbittorrent:
network_mode: "service:gluetun"
# no ports: block hereEin expose:-Block beim abhängigen Dienst ist ebenfalls wirkungslos. Ein networks:-Block dort verhindert den Start vollständig: Compose meldet, dass der Dienst sich gegenseitig ausschließende network_mode und networks deklariert, und verweigert das Laden der Datei insgesamt.
Eine Folge zeigt sich erst später. Alle Container im Namespace teilen sich einen gemeinsamen Portbereich. Deshalb kollidieren zwei Anwendungen, die beide standardmäßig 8080 verwenden. Die Anwendung, die als Zweite startet, schlägt dann mit einem Fehler wegen einer bereits verwendeten Adresse fehl. Ändern Sie den Port in der Konfiguration einer der Anwendungen, zum Beispiel über die Variable WEBUI_PORT im LinuxServer-qBittorrent-Image. Veröffentlichen Sie anschließend die neue Nummer auf gluetun.
Wie erreichen sich Container hinter gluetun?
Innerhalb des Namespace verwenden sie bereits gemeinsam ein Loopback-Interface. Ein Container hinter gluetun erreicht seinen Schwestercontainer unter 127.0.0.1:<port>, ohne dass ein Docker-Netzwerk beteiligt ist.
Außerhalb des Namespace hat der Container keinen Namen. Der integrierte DNS-Dienst von Docker löst einen Servicenamen in die Adresse dieses Dienstes in einem benutzerdefinierten Netzwerk auf. Dieser Container hat jedoch in keinem Netzwerk eine Adresse. Ein normaler Container wie Sonarr erreicht den Torrent-Client daher nicht unter http://qbittorrent:8080. Er erreicht ihn unter http://gluetun:8080, weil der Socket im Namespace von gluetun und unter der Adresse von gluetun auf Verbindungen wartet. Das überrascht Anwender, die wissen, wie Docker-Compose-Netzwerke und Servicenamen funktionieren, und erwarten, dass die übliche Namensauflösung gilt. Es funktioniert auch ohne eine Veröffentlichung auf dem Host, da sich beide Container im selben Compose-Netzwerk befinden.
Prüfen Sie den DNS zuerst, bevor Sie etwas anderes untersuchen. Gluetun verwendet seinen eigenen Resolver und schreibt /etc/resolv.conf in seinen eigenen Container. /etc/resolv.conf ist jedoch eine datei pro Container. Die von gluetun geschriebene Datei ist daher nicht die Datei, die Ihre Anwendung liest.
docker exec qbittorrent cat /etc/resolv.confWie erreiche ich einen Dienst, der auf dem Docker-Host ausgeführt wird?
Verwenden Sie host.docker.internal. Dafür sind zwei Einstellungen an zwei verschiedenen Stellen erforderlich, weil zwei unterschiedliche Probleme behoben werden müssen.
Der Name kommt zuerst. /etc/hosts gilt pro Container. Der Eintrag extra_hosts gehört daher in den Anwendungscontainer, nicht in gluetun.
prowlarr:
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"host-gateway ist ein spezieller Wert, den Docker durch eine interne Adresse des Hosts selbst ersetzt. Bei einer normalen Docker-Installation unter Linux ist das die Adresse der docker0-Bridge, häufig 172.17.0.1. Prüfen Sie den Wert auf dem VPS mit ip -4 addr show docker0. Docker Desktop löst diesen Namen selbst auf. Deshalb lassen Anleitungen für einen Laptop die Zeile extra_hosts aus. Dieselbe Datei schlägt dann auf einem Server fehl.
Die Route kommt danach. Durch das Hinzufügen des Namens weiß der Container nur, welche Adresse er verwenden soll. Das Paket verlässt den Container weiterhin über die Standardroute von gluetun. Diese führt durch den Tunnel, und die Firewall von gluetun verwirft das Paket. Das Symptom ist eine Verbindung, die zunächst hängt und anschließend in einen Timeout läuft, nicht eine abgelehnte Verbindung. Eine Ablehnung bedeutet, dass das Paket angekommen ist und eine Gegenstelle mit einer Ablehnung geantwortet hat. Ein Timeout bedeutet, dass es nie angekommen ist.
gluetun:
environment:
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32Prüfen Sie anschließend, ob der Dienst auf dem Host tatsächlich an dieser Adresse lauscht. Ein PostgreSQL-Server, der nur an 127.0.0.1 gebunden ist, ist aus keinem Container erreichbar, unabhängig davon, ob ein Tunnel verwendet wird. Der Wert 127.0.0.1 bezeichnet innerhalb des Namespace den Loopback des jeweiligen Namespace. Binden Sie den Dienst stattdessen an 172.17.0.1. Dann akzeptiert er Verbindungen aus Containern, bleibt aber von der öffentlichen Schnittstelle getrennt. Prüfen Sie dies auf dem Host mit ss -lntp | grep 5432.
Was FIREWALL_OUTBOUND_SUBNETS tatsächlich ändert
Die gluetun-Dokumentation beschreibt den Wert als kommaseparierte Subnetze, auf die gluetun und die Container mit gemeinsamem Netzwerk-Stack zugreifen dürfen. Sie weist außerdem darauf hin, dass dafür Änderungen an Firewall und Routing erforderlich sind. Beide Aspekte sind relevant. Gluetun fügt für jedes angegebene Subnetz eine Route über das Docker-Bridge-Gateway hinzu. Pakete für diese Adressen verlassen das System daher über eth0 statt über den Tunnel. Außerdem öffnet gluetun die Firewall für diese Ziele. Andernfalls verwirft gluetun ausgehenden Datenverkehr, der nicht an den VPN-Server gerichtet ist.
Geben Sie den Wert ohne Leerzeichen nach den Kommas an.
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32Zwei Eigenschaften werden leicht übersehen. Dies ist eine Einstellung auf Namespace-Ebene. Sie gilt daher für jeden Container hinter gluetun, nicht nur für den vorgesehenen Container. Außerdem gilt sie ausschließlich für ausgehenden Datenverkehr. Sie steuert Verbindungen, die ein Container selbst startet. Verbindungen, die an einem veröffentlichten Port eingehen, nehmen einen anderen Pfad und benötigen keinen Eintrag an dieser Stelle.
Die Weboberfläche von einem Tailscale-Peer aus erreichen
Tailscale weist jedem Rechner eine Adresse aus 100.64.0.0/10 zu, dem für Carrier-Grade-NAT reservierten Bereich. Für die beiden Richtungen sind unterschiedliche Maßnahmen erforderlich.
Eingehender Datenverkehr ist einfach. Das Veröffentlichen von 8080:8080 auf gluetun bindet diesen Port an alle Adressen des Hosts. Die tailscale0-Schnittstelle des Hosts ist eine davon. Ein Peer öffnet daher http://<machine-name>:8080 und erreicht den Container. Gluetun ist an diesem Pfad nicht beteiligt, weil die NAT-Regel von Docker auf dem Host außerhalb des Namespace liegt.
Damit die Benutzeroberfläche nur über das Tailnet erreichbar ist, binden Sie den veröffentlichten Port an die Tailscale-Adresse des Hosts und nicht an alle Adressen.
ports:
- "100.101.102.103:8080:8080/tcp"Ermitteln Sie diese Adresse auf dem Host mit tailscale ip -4. Das Binden ist hier eine wirksamere Kontrolle als eine Firewall-Regel, weil der Port an der öffentlichen Schnittstelle überhaupt nicht geöffnet wird. Außerdem umgehen Sie damit das Problem aus Docker veröffentlicht Ports direkt an ufw vorbei.
Beim ausgehenden Datenverkehr kommt FIREWALL_OUTBOUND_SUBNETS zum Einsatz. Wenn ein Container einen Peer erreichen muss, fügen Sie die Adresse dieses Peers hinzu. Bevorzugen Sie pro Peer ein /32 statt des gesamten /10. MagicDNS-Namen werden im Container nicht aufgelöst, weil der Container nicht den Resolver des Hosts verwendet. Verwenden Sie daher die numerische 100.x-Adresse oder hinterlegen Sie sie mit einer extra_hosts-Zeile. Das gilt auch, wenn Sie Ihren eigenen Tailscale-Steuerungsserver mit Headscale betreiben.
Eine vollständige Compose-Datei für das übliche Schema
Ein Download-Client hinter dem VPN, zwei Web-UIs, die nur im Tailnet antworten, und ein Container, der eine auf dem Host laufende PostgreSQL-Datenbank liest.
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-stoppedLesen Sie die Datei im Hinblick auf das Muster und nicht auf die Produktnamen. Beide UIs werden auf gluetun veröffentlicht und an die Tailnet-Adresse des Hosts gebunden. Sie antworten daher über Tailscale und nirgendwo sonst. Nur Prowlarr enthält die Zeile extra_hosts, weil Prowlarr der Container ist, der host.docker.internal auflöst. FIREWALL_OUTBOUND_SUBNETS nennt zwei einzelne Adressen: die Docker-Bridge-Adresse des Hosts, damit Prowlarr eine Datenbankverbindung öffnen kann, und einen Tailnet-Peer.
Der PostgreSQL-Server fehlt absichtlich in der Datei. Er läuft auf dem VPS als gewöhnlicher Systemdienst und lauscht auf 172.17.0.1:5432. Das entspricht derselben Schichtung wie bei einem arr-Stack mit Docker Compose, wobei die Datenbank aus Docker heraus verlagert wurde.
Halten Sie den privaten WireGuard-Schlüssel aus der Compose-Datei heraus. ${WIREGUARD_PRIVATE_KEY} liest ihn aus einer .env-Datei daneben. Dieses Muster wird unter Umgebungsdateien und Secrets für Docker Compose behandelt. Die Klausel condition: service_healthy verwendet den Healthcheck, den das gluetun-Image bereits mitliefert. Dadurch startet nichts, bevor der Tunnel seinen betriebsbereiten Zustand meldet. Compose-Healthchecks erläutern die allgemeine Form.
Auf jeder Adresse veröffentlichen statt nur im Tailnet
Entfernen Sie das Adresspräfix und die Port-Bindings auf 0.0.0.0. Dadurch wird auch die öffentliche IP-Adresse des VPS eingebunden. Tun Sie dies nur hinter einer Firewall, die Sie kontrollieren, und lesen Sie zuerst den Hinweis zu ufw weiter oben.
ports:
- "8080:8080/tcp"Bestätigen, dass der Tunnel weiterhin Datenverkehr überträgt
Führen Sie dieselbe Anfrage zweimal aus: einmal innerhalb des Namespace und einmal auf dem Host. Vergleichen Sie anschließend die Ergebnisse.
docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.orgDie erste Ausgabe sollte die Exit-Adresse Ihres VPN-Providers anzeigen. Die zweite Ausgabe sollte die VPS-Adresse anzeigen. Stimmen die Adressen überein, läuft der Datenverkehr des Containers nicht durch den Tunnel. Dann sind alle weiteren Maßnahmen in dieser Anleitung wirkungslos, bis dieses Problem behoben ist.
Die Routingtabelle zeigt, welcher Datenverkehr den Tunnel verwendet und welcher nicht.
docker run --rm --network=container:gluetun alpine:3.22 ip route showDie Standardroute sollte auf die Tunnelschnittstelle tun0 zeigen. Darunter sollte für jeden Eintrag in FIREWALL_OUTBOUND_SUBNETS eine Route angezeigt werden, die auf das Docker-Bridge-Gateway zeigt. Jede andere Route über eth0 umgeht das VPN.
Der Control Server von Gluetun meldet unter /v1/publicip/ip auf Port 8000 dieselbe öffentliche IP-Adresse. In neueren Versionen müssen Sie die Authentifizierung für die Routen des Control Servers konfigurieren. Richten Sie diese daher ein, bevor Sie sich darauf verlassen.
Die Sicherheitslücke durch ein falsches Subnetz
FIREWALL_OUTBOUND_SUBNETS ist eine absichtlich in der Firewall geöffnete Stelle. Die Größe dieser Öffnung entspricht daher der Größe des Risikos. Vier Möglichkeiten, sie zu groß zu machen:
0.0.0.0/0leitet den gesamten Datenverkehr außerhalb des Tunnels weiter. Die beiden IP-Prüfungen oben erkennen dies bereits beim ersten Durchlauf, weil sie dieselbe Adresse zurückgeben.- Ein Bereich, der größer als das Ziel ist. Wenn Sie
10.0.0.0/8öffnen, um einen einzelnen Host unter10.0.1.7zu erreichen, wird gleichzeitig jede Adresse geöffnet, die ein Torrent-Peer in diesem Bereich angeben könnte. Verwenden Sie10.0.1.7/32. - Ein Bereich, der die eigenen Adressen des Tunnels überschneidet. Die gluetun-Dokumentation warnt davor, dass gluetun den VPN-Datenverkehr dann stattdessen über die Bridge weiterleitet. Dadurch funktioniert die Portweiterleitung nicht mehr. Prüfen Sie den Wert
WIREGUARD_ADDRESSES, bevor Sie einen privaten Bereich öffnen. 100.64.0.0/10für Tailscale. Dadurch werden ungefähr vier Millionen Adressen geöffnet, damit ein Peer erreichbar ist. Führen Sie die benötigten Peers als/32-Einträge auf.
Beachten Sie, dass diese Einstellung für den gesamten Namespace gilt. Wenn Sie ein Subnetz öffnen, damit ein Indexer einen Hostdienst erreichen kann, wird dasselbe Subnetz auch für den Torrent-Client geöffnet, der diesen Namespace verwendet. Führen Sie die Prüfung der öffentlichen IP-Adresse nach jeder Änderung dieser Variable erneut aus. Nur diese Prüfung zeigt, ob die Änderung die gewünschte Wirkung hatte.
Was beim Neustart von gluetun ausfällt
gluetun verwaltet den Namespace. Deshalb entspricht der Lebenszyklus von gluetun dem Lebenszyklus des Namespace. Wenn Sie einen abhängigen Container starten, während gluetun nicht läuft, schlägt der Start sofort fehl:
Error response from daemon: cannot join network of a non running containerDas Neustarten von gluetun an Ort und Stelle führt zu einem weniger offensichtlichen Fehler. Die abhängigen Container laufen weiter, während der Namespace, an den sie angebunden waren, im Hintergrund neu erstellt wird. Daher meldet docker ps, dass alles ordnungsgemäß funktioniert, aber kein Dienst antwortet. Erstellen Sie nach jeder Änderung am gluetun-Dienst die gesamte Gruppe neu, statt nur einen Teil davon neu zu starten.
docker compose up -d --force-recreateDas gilt auch für Image-Aktualisierungen. Wenn Sie ein neues gluetun-Image abrufen und nur diesen Dienst neu erstellen, verweisen die anderen Container weiterhin auf einen Namespace, der nicht mehr existiert.
FAQ
Warum meldet Docker „port publishing and the container type network mode“?
Das passiert, weil sich in einem Dienst noch ein ports:-Block befindet, der gleichzeitig network_mode: service:gluetun setzt. Das Veröffentlichen eines Ports fügt eine NAT-Regel hinzu, die einen Host-Port an den eigenen Netzwerk-Namespace eines Containers weiterleitet. Ein Container in diesem Modus hat keinen eigenen Netzwerk-Namespace. Löschen Sie den ports:-Block aus diesem Dienst und fügen Sie dieselbe Zuordnung beim gluetun-Dienst hinzu. Die Portnummer bleibt unverändert, weil die Anwendung weiterhin innerhalb des gemeinsam genutzten Namespace auf diesem Port lauscht.
Wie erreichen andere Container einen Dienst hinter gluetun?
Container im selben Namespace erreichen einander über 127.0.0.1. Container außerhalb dieses Namespace verwenden den Namen des gluetun-Dienstes. Daher funktioniert http://gluetun:8080, während http://qbittorrent:8080 nicht funktioniert. Der Anwendungscontainer hat keine Adresse in einem Docker-Netzwerk. Der integrierte DNS-Server kann seinen Namen daher nicht auflösen. Dafür ist keine Portweiterleitung erforderlich, solange beide Container ein Compose-Netzwerk gemeinsam verwenden.
Was sollte ich in FIREWALL_OUTBOUND_SUBNETS eintragen?
Tragen Sie nur die Adressen ein, zu denen ein Container hinter gluetun eine Verbindung initiieren muss. Verwenden Sie dabei die kleinstmöglichen Netze. Ein einzelner Rechner wird als /32 angegeben. Die beiden häufigsten Einträge sind der Docker-Host unter 172.17.0.1/32 und jeweils ein /32 für jeden Tailscale-Peer, den Sie erreichen. Fügen Sie niemals 0.0.0.0/0 hinzu. Fügen Sie außerdem niemals einen Bereich hinzu, der sich mit den eigenen Tunneladressen Ihres VPN überschneidet. Eingehende Verbindungen zu einem veröffentlichten Port benötigen hier keinen Eintrag.
Warum kann der Container meine Tailscale-MagicDNS-Namen nicht auflösen?
MagicDNS funktioniert, indem der Resolver des Hosts auf den DNS-Server von Tailscale verwiesen wird. Der Container verwendet jedoch nicht den Resolver des Hosts. Er verwendet den Wert aus seinem eigenen /etc/resolv.conf. Hinter gluetun ist dies die DNS-Konfiguration von gluetun. Prüfen Sie dies mit docker exec <container> cat /etc/resolv.conf. Verwenden Sie die numerische 100.x-Adresse des Peers oder hinterlegen Sie den Namen mit einem extra_hosts-Eintrag in diesem Container.
Wie prüfe ich, ob der Datenverkehr weiterhin über das VPN läuft?
Führen Sie eine Anfrage innerhalb des Namespace und dieselbe Anfrage auf dem Host aus. Vergleichen Sie anschließend die Antworten. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org sollte die Exit-Adresse Ihres VPN-Anbieters zurückgeben. curl -s https://api.ipify.org sollte auf dem VPS die Adresse des VPS zurückgeben. Zwei übereinstimmende Antworten bedeuten, dass der Tunnel den Datenverkehr des Containers nicht weiterleitet. Wiederholen Sie diese Prüfung nach jeder Änderung an FIREWALL_OUTBOUND_SUBNETS.