SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-13

Gluetun: komunikacja z hostem i innymi kontenerami

Kontenery w sieci gluetun nie posiadają własnych interfejsów. Dowiedz się, jak poprawnie publikować porty oraz ograniczać dostęp do podsieci lokalnych przy użyciu reguł iptables.

Co dzieje się, gdy kontener dołącza do sieci gluetun

Kontener, w którym ustawiono network_mode: service:gluetun, nie posiada własnych interfejsów sieciowych. Dołącza on do przestrzeni nazw sieciowej (network namespace) usługi gluetun, dlatego publikowanie portów oraz reguły zapory sieciowej przestają być właściwościami tego kontenera, a stają się właściwościami usługi gluetun. Każda odpowiedź poniżej wynika z tego jednego faktu.

Przestrzeń nazw sieciowych to prywatna kopia stosu sieciowego w jądrze systemu: posiada własne interfejsy, własną tablicę routingu, własne reguły zapory oraz własne gniazda nasłuchujące. Docker domyślnie przydziela każdemu kontenerowi taką przestrzeń. Gdy użyjesz network_mode: service:gluetun, Docker pomija ten krok i umieszcza nowy kontener wewnątrz przestrzeni nazw, która już należy do gluetun. Kontener zachowuje własny system plików oraz własny plik /etc/hosts, co będzie miało znaczenie w dalszej części.

Można to sprawdzić bezpośrednio.

docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrent

Polecenie to wyświetli container: oraz identyfikator kontenera gluetun, podczas gdy zwykły kontener wyświetliłby bridge. Ten przewodnik stanowi kontynuację tematu kierowania ruchu Docker przez VPN za pomocą gluetun: tunel działa, a teraz należy zapewnić komunikację z kontenerem.

Publikowanie portu w gluetun, a nie w aplikacji

Pozostawienie bloku ports: w usłudze, która ustawia network_mode, powoduje, że Docker odmawia utworzenia kontenera:

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

Przyczyna jest bezpośrednia. Publikowanie portu oznacza dodanie reguły NAT (network address translation), która przekierowuje port hosta do przestrzeni nazw sieciowej kontenera, a ten kontener jej nie posiada. Przenieś mapowanie do usługi gluetun. Numer portu nie ulega zmianie, ponieważ aplikacja nadal nasłuchuje na tym porcie wewnątrz współdzielonej przestrzeni nazw.

services:
  gluetun:
    ports:
      - "8080:8080/tcp"   # qBittorrent web UI

  qbittorrent:
    network_mode: "service:gluetun"
    # no ports: block here

Blok expose: w usłudze zależnej jest równie bezcelowy, a blok networks: w tym miejscu stanowi twardą blokadę: Compose zgłasza, że usługa deklaruje wzajemnie wykluczające się network_mode oraz networks i całkowicie odmawia wczytania pliku.

Jedna konsekwencja ujawnia się później. Każdy kontener w przestrzeni nazw współdzieli jedną przestrzeń portów, więc dwie aplikacje, z których obie domyślnie korzystają z 8080, wchodzą w konflikt, a ta, która uruchamia się jako druga, kończy działanie błędem "address already in use". Zmień numer portu jednej z nich w jej własnej konfiguracji, na przykład za pomocą zmiennej WEBUI_PORT w obrazie LinuxServer qBittorrent, a następnie opublikuj nowy numer w gluetun.

W jaki sposób kontenery za gluetun komunikują się ze sobą?

Wewnątrz przestrzeni nazw współdzielą one interfejs loopback. Kontener znajdujący się za gluetun łączy się ze swoim odpowiednikiem pod adresem 127.0.0.1:<port> bez udziału sieci Docker.

Spoza przestrzeni nazw kontener nie posiada nazwy. Wbudowany serwer DNS Dockera rozwiązuje nazwę usługi na adres w sieci zdefiniowanej przez użytkownika, a ten kontener nie posiada adresu w żadnej sieci. Dlatego standardowy kontener, taki jak Sonarr, nie łączy się z klientem torrent pod adresem http://qbittorrent:8080. Łączy się z nim pod adresem http://gluetun:8080, ponieważ gniazdo nasłuchuje w przestrzeni nazw gluetun, na adresie przypisanym do gluetun. Jest to zaskakujące dla osób, które znają zasady działania sieci Docker Compose i nazw usług i oczekują zastosowania standardowego nazewnictwa. Działa to również bez publikowania jakichkolwiek portów na hoście, ponieważ oba kontenery znajdują się w tej samej sieci Compose.

Przed przystąpieniem do dalszego debugowania należy sprawdzić DNS. Gluetun uruchamia własny resolver i nadpisuje plik /etc/resolv.conf wewnątrz własnego kontenera, jednak /etc/resolv.conf jest plikiem przypisanym do każdego kontenera z osobna, więc plik zmodyfikowany przez gluetun nie jest tym, który odczytuje aplikacja.

docker exec qbittorrent cat /etc/resolv.conf

Jak uzyskać dostęp do usługi działającej na hoście Docker?

Należy użyć host.docker.internal. Wymaga to dwóch ustawień w dwóch różnych miejscach, ponieważ przyczyną problemu są dwie odrębne kwestie.

Najpierw nazwa. /etc/hosts jest przypisane do kontenera, więc wpis extra_hosts musi znaleźć się w kontenerze aplikacji, a nie w gluetun.

  prowlarr:
    network_mode: "service:gluetun"
    extra_hosts:
      - "host.docker.internal:host-gateway"

host-gateway to specjalna wartość, którą Docker zastępuje wewnętrznym adresem samego hosta. W standardowej instalacji Docker na systemie Linux jest to adres mostu docker0, zazwyczaj 172.17.0.1. Należy potwierdzić własny adres za pomocą ip -4 addr show docker0 na serwerze VPS. Docker Desktop samodzielnie rozwiązuje tę nazwę, dlatego poradniki pisane na laptopy pomijają linię extra_hosts, co powoduje błąd w tym samym pliku na serwerze.

Następnie trasa. Dodanie nazwy informuje kontener jedynie o tym, jakiego adresu użyć. Pakiet nadal opuszcza kontener przez domyślną trasę gluetun, czyli tunel, a zapora sieciowa gluetun go odrzuca. Objawem jest połączenie, które zawiesza się i przekracza limit czasu, zamiast zostać odrzucone. Odmowa oznacza, że pakiet dotarł i otrzymał odpowiedź odmowną. Przekroczenie limitu czasu oznacza, że pakiet nigdy nie dotarł do celu.

  gluetun:
    environment:
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32

Następnie należy sprawdzić, czy usługa na hoście faktycznie nasłuchuje na tym adresie. Serwer PostgreSQL powiązany tylko z 127.0.0.1 jest nieosiągalny z poziomu jakiegokolwiek kontenera, niezależnie od tunelu, ponieważ 127.0.0.1 wewnątrz przestrzeni nazw jest jej własnym interfejsem zwrotnym (loopback). Należy powiązać go z 172.17.0.1: akceptuje to połączenia z kontenerów, pozostając jednocześnie poza interfejsem publicznym. Weryfikację należy przeprowadzić za pomocą ss -lntp | grep 5432 na hoście.

Co faktycznie zmienia zmienna FIREWALL_OUTBOUND_SUBNETS

Dokumentacja gluetun opisuje tę zmienną jako listę podsieci oddzielonych przecinkami, do których gluetun oraz kontenery współdzielące jego stos sieciowy mają dostęp. Zaznacza również, że wiąże się to ze zmianami w zaporze sieciowej oraz tablicy routingu. Obie te kwestie są istotne. Gluetun dodaje trasę dla każdej wymienionej podsieci przez bramę mostu Docker, dzięki czemu pakiety dla tych adresów opuszczają system przez eth0, a nie przez tunel. Zmienna ta otwiera również zaporę sieciową dla tych podsieci, ponieważ w przeciwnym razie gluetun odrzuca ruch wychodzący, który nie jest kierowany do serwera VPN.

Wartość należy wpisać bez spacji po przecinkach.

      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32

Łatwo pominąć dwie właściwości tego ustawienia. Po pierwsze, jest to ustawienie na poziomie przestrzeni nazw, więc dotyczy każdego kontenera znajdującego się za gluetun, a nie tylko tego, który był pierwotnie brany pod uwagę. Po drugie, dotyczy ono wyłącznie ruchu wychodzącego: reguluje połączenia inicjowane przez kontener. Połączenia przychodzące na opublikowany port korzystają z innej ścieżki i nie wymagają wpisu w tym miejscu.

Dostęp do interfejsu WWW z węzła Tailscale

Tailscale nadaje każdej maszynie adres w 100.64.0.0/10, czyli zakresie zarezerwowanym dla carrier grade NAT. Oba kierunki ruchu wymagają odrębnej konfiguracji.

Ruch przychodzący jest prostszy. Publikacja 8080:8080 w gluetun wiąże ten port ze wszystkimi adresami hosta, a interfejs tailscale0 jest jednym z nich, więc węzeł otwiera http://<machine-name>:8080 i uzyskuje dostęp do kontenera. Gluetun nie bierze udziału w tym procesie, ponieważ reguła NAT Dockera znajduje się na hoście, poza przestrzenią nazw kontenera.

Aby interfejs był dostępny wyłącznie przez tailnet, należy powiązać opublikowany port z adresem Tailscale hosta, zamiast ze wszystkimi dostępnymi adresami.

    ports:
      - "100.101.102.103:8080:8080/tcp"

Adres ten można sprawdzić za pomocą tailscale ip -4 na hoście. Powiązanie portu z konkretnym adresem jest skuteczniejszą metodą kontroli niż reguła firewalla, ponieważ port w ogóle nie jest otwierany na publicznym interfejsie. Pozwala to również ominąć problem opisany w publikowaniu portów przez Docker z pominięciem ufw.

Ruch wychodzący to sytuacja, w której powraca FIREWALL_OUTBOUND_SUBNETS. Jeśli kontener musi nawiązać połączenie z węzłem, należy dodać adres tego węzła, preferując /32 dla każdego węzła zamiast całego /10. Nazwy MagicDNS nie będą rozwiązywane wewnątrz kontenera, ponieważ kontener nie korzysta z resolvera hosta, dlatego należy użyć numerycznego adresu 100.x lub przypisać go na stałe za pomocą linii extra_hosts. To samo dotyczy sytuacji, w której uruchamiasz własny serwer kontrolny Tailscale za pomocą Headscale.

Kompletny plik compose dla typowej konfiguracji

Klient pobierający dane za VPN, dwa interfejsy webowe dostępne wyłącznie w sieci tailnet oraz jeden kontener odczytujący bazę danych PostgreSQL działającą na hoście.

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

Należy analizować plik pod kątem wzorca, a nie nazw produktów. Oba interfejsy webowe są publikowane przez gluetun i powiązane z adresem tailnet hosta, dzięki czemu odpowiadają wyłącznie w sieci Tailscale. Tylko Prowlarr zawiera linię extra_hosts, ponieważ jest to kontener, który rozwiązuje host.docker.internal. FIREWALL_OUTBOUND_SUBNETS wskazuje dwa pojedyncze adresy: adres mostu Docker hosta, aby Prowlarr mógł nawiązać połączenie z bazą danych, oraz adres jednego węzła sieci tailnet.

Serwer PostgreSQL celowo nie został uwzględniony w pliku. Działa on na VPS jako zwykła usługa systemowa nasłuchująca na 172.17.0.1:5432. Jest to ta sama warstwowa struktura, co w przypadku stosów arr w Docker Compose, z bazą danych przeniesioną poza środowisko Docker.

Klucz prywatny WireGuard powinien znajdować się poza plikiem compose. ${WIREGUARD_PRIVATE_KEY} odczytuje dane z pliku .env znajdującego się obok, zgodnie ze wzorcem opisanym w plikach env i sekretach dla Docker Compose. Klauzula condition: service_healthy wykorzystuje mechanizm healthcheck dostarczany przez obraz gluetun, dzięki czemu żadna usługa nie uruchomi się, dopóki tunel nie zgłosi gotowości. Healthchecki w Compose wyjaśniają ogólną zasadę działania.

Publikowanie na wszystkich adresach zamiast tylko w sieci tailnet

Należy usunąć prefiks adresu oraz powiązania portów w 0.0.0.0, co obejmuje publiczny adres IP serwera VPS. Należy to robić wyłącznie za kontrolowanym firewallem, pamiętając o wcześniejszym zapoznaniu się z uwagami dotyczącymi ufw.

    ports:
      - "8080:8080/tcp"

Weryfikacja przesyłu ruchu przez tunel

Wykonaj dwukrotnie to samo zapytanie: raz z wnętrza przestrzeni nazw (namespace), a raz z poziomu hosta, a następnie porównaj wyniki.

docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.org

Pierwsze polecenie powinno zwrócić adres wyjściowy dostawcy VPN. Drugie powinno zwrócić adres VPS. Jeśli adresy są identyczne, ruch kontenera nie przechodzi przez tunel, a wszelkie poprawki opisane w tym przewodniku są bezcelowe, dopóki ten stan nie zostanie naprawiony.

Tablica routingu wskazuje, który ruch jest kierowany przez tunel, a który nie.

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

Domyślna trasa powinna wskazywać na interfejs tunelu, tun0. Poniżej powinna znajdować się jedna trasa dla każdego wpisu w FIREWALL_OUTBOUND_SUBNETS, wskazująca na bramę mostu Docker. Każda inna trasa wychodząca przez eth0 oznacza ruch, który omija VPN.

Serwer sterujący Gluetun raportuje ten sam publiczny adres IP na porcie 8000, pod adresem /v1/publicip/ip. Nowsze wersje wymagają skonfigurowania uwierzytelniania dla tras serwera sterującego, dlatego należy to skonfigurować przed poleganiem na tym rozwiązaniu.

Wyciek spowodowany błędną podsiecią

FIREWALL_OUTBOUND_SUBNETS to celowo wykonana dziura w zaporze sieciowej, zatem rozmiar ryzyka odpowiada rozmiarowi tej dziury. Oto cztery sposoby na nadmierne jej powiększenie:

  • 0.0.0.0/0 wysyła cały ruch poza tunel. Dwie powyższe kontrole IP wykryją to przy pierwszym uruchomieniu, ponieważ zwrócą ten sam adres.
  • Zakres szerszy niż docelowy. Otwarcie 10.0.0.0/8 w celu uzyskania dostępu do jednej maszyny pod adresem 10.0.1.7 otwiera również każdy adres, który peer sieci torrent może ogłosić w tym zakresie. Należy wpisać 10.0.1.7/32.
  • Zakres pokrywający się z adresami samego tunelu. Dokumentacja gluetun ostrzega, że powoduje to wysyłanie ruchu VPN przez most (bridge), co przerywa przekierowanie portów. Przed otwarciem jakiegokolwiek prywatnego zakresu należy sprawdzić wartość WIREGUARD_ADDRESSES.
  • 100.64.0.0/10 w przypadku Tailscale. Oznacza to otwarcie około czterech milionów adresów, aby uzyskać dostęp do jednego peera. Należy wymienić potrzebne peery jako wpisy /32.

Należy pamiętać, że to ustawienie obejmuje całą przestrzeń nazw. Otwarcie podsieci, aby indeksator mógł uzyskać dostęp do usługi hosta, otwiera tę samą podsieć dla klienta sieci torrent współdzielącego tę przestrzeń nazw. Po każdej zmianie tej zmiennej należy ponownie wykonać test publicznego adresu IP, ponieważ jest to jedyny test potwierdzający, czy zmiana przyniosła zamierzony skutek.

Co ulega awarii podczas restartu gluetun

Gluetun zarządza przestrzenią nazw (namespace), dlatego cykl życia gluetun jest tożsamy z cyklem życia tej przestrzeni. Uruchomienie kontenera zależnego w czasie, gdy gluetun nie działa, kończy się natychmiastowym błędem:

Error response from daemon: cannot join network of a non running container

Restartowanie gluetun w miejscu jest awarią mniej oczywistą. Kontenery zależne działają nadal, podczas gdy przestrzeń nazw, do której są przypisane, jest przebudowywana pod nimi, więc docker ps raportuje poprawny stan, mimo że żadna usługa nie odpowiada. Po każdej zmianie w usłudze gluetun należy odtworzyć całą grupę, zamiast restartować tylko jej część.

docker compose up -d --force-recreate

Ta sama zasada dotyczy aktualizacji obrazów. Pobranie nowego obrazu gluetun i odtworzenie tylko tej jednej usługi sprawia, że pozostałe kontenery wskazują na przestrzeń nazw, która już nie istnieje.

FAQ

Dlaczego Docker zgłasza błąd dotyczący publikowania portów i trybu sieci kontenera?

Ponieważ blok ports: nadal znajduje się w usłudze, która ma również ustawione network_mode: service:gluetun. Publikowanie portu dodaje regułę NAT przekierowującą port hosta do własnej przestrzeni sieciowej kontenera, a kontener w tym trybie jej nie posiada. Usuń blok ports: z tej usługi i dodaj identyczne mapowanie do usługi gluetun. Numer portu pozostaje bez zmian, ponieważ aplikacja nadal nasłuchuje na nim wewnątrz współdzielonej przestrzeni nazw.

Jak inne kontenery mogą połączyć się z usługą działającą za gluetun?

Kontenery wewnątrz tej samej przestrzeni nazw komunikują się ze sobą przez 127.0.0.1. Kontenery znajdujące się poza nią używają nazwy usługi gluetun, więc http://gluetun:8080 działa tam, gdzie http://qbittorrent:8080 nie działa. Kontener aplikacji nie posiada adresu w żadnej sieci Docker, więc wbudowany serwer DNS nie ma czego rozwiązać dla jego nazwy. Publikowanie portów nie jest do tego potrzebne, o ile oba kontenery współdzielą sieć Compose.

Co należy wpisać w FIREWALL_OUTBOUND_SUBNETS?

Wyłącznie adresy, z którymi kontener za gluetun musi nawiązać połączenie, zapisane w jak najbardziej zawężony sposób. Pojedyncza maszyna to /32. Dwa typowe wpisy to host Docker pod adresem 172.17.0.1/32 oraz jeden /32 dla każdego węzła Tailscale, z którym się łączysz. Nigdy nie dodawaj 0.0.0.0/0 ani zakresu, który pokrywa się z adresami tunelu VPN. Połączenia przychodzące na opublikowany port nie wymagają tutaj żadnego wpisu.

Dlaczego kontener nie może rozwiązać moich nazw Tailscale MagicDNS?

MagicDNS działa poprzez wskazanie resolvera hosta na serwer DNS Tailscale, a kontener nie korzysta z resolvera hosta. Używa on tego, co wskazuje jego własny /etc/resolv.conf, którym za gluetun jest konfiguracja DNS usługi gluetun. Potwierdź to za pomocą docker exec <container> cat /etc/resolv.conf. Użyj numerycznego adresu 100.x danego węzła lub przypisz nazwę za pomocą wpisu extra_hosts w tym kontenerze.

Jak potwierdzić, że ruch nadal przechodzi przez VPN?

Wykonaj jedno żądanie z wnętrza przestrzeni nazw oraz to samo żądanie z poziomu hosta, a następnie porównaj odpowiedzi. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org powinno zwrócić adres wyjściowy dostawcy VPN, podczas gdy curl -s https://api.ipify.org na VPS zwróci adres VPS. Dwie identyczne odpowiedzi oznaczają, że tunel nie obsługuje ruchu kontenera. Wykonaj to sprawdzenie ponownie po każdej zmianie w FIREWALL_OUTBOUND_SUBNETS.