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

Docker: jak przekierować ruch kontenera przez VPN

Dowiedz się, dlaczego po podpięciu kontenera do sieci Gluetun porty przestają działać. Wyjaśniamy mechanizm przestrzeni nazw i udostępniamy gotowy plik compose dla VPN.

Dlaczego porty znikają podczas kierowania kontenerów Docker przez VPN

Aby skierować kontenery Docker przez VPN, należy przypisać tunel do jednego kontenera, a następnie podłączyć pozostałe do jego przestrzeni nazw sieciowej za pomocą network_mode: "service:gluetun". To właśnie to podłączenie jest elementem zaskakującym dla użytkowników. Podłączony kontener przestaje posiadać własną sieć, więc jego opublikowane porty oraz nazwa usługi Docker przestają być dostępne. Należy opublikować porty w kontenerze VPN, a pozostałe kontenery będą uzyskiwać dostęp do aplikacji poprzez nazwę kontenera VPN.

Pozostawienie bloku ports: w podłączonym kontenerze spowoduje, że Docker odmówi jego utworzenia:

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

Narzędziem wykorzystywanym w tym celu jest Gluetun, kontener łączący się z komercyjnym dostawcą VPN (virtual private network) za pośrednictwem protokołu WireGuard lub OpenVPN, posiadający własny firewall. Wersja v3.41.3 jest aktualna na sierpień 2026. Przykłady wykorzystują Mullvad z WireGuard, dlatego wymagane jest posiadanie konta oraz klucza od dostawcy. Jeśli wolisz zakończyć tunel na własnym sprzęcie, uruchomienie własnego serwera WireGuard na VPS pozwala na zbudowanie drugiego końca połączenia, a wg-easy w Docker opakowuje to rozwiązanie w interfejs WWW.

Działanie network_mode: "service:gluetun"

Każdy kontener Docker domyślnie otrzymuje własną przestrzeń nazw sieci (network namespace): własne interfejsy, tablicę routingu, reguły firewalla oraz gniazda nasłuchujące. Tryb service: pomija ten krok i uruchamia kontener wewnątrz przestrzeni nazw gluetun. Jedna przestrzeń nazw oznacza jeden adres IP, co zmienia sześć kwestii.

  • Aplikacja nie posiada własnego adresu. Jej adresem jest adres gluetun.
  • Aplikacja nie jest podłączona do żadnej sieci Docker, więc jej nazwa usługi nie jest rejestrowana i nie podlega rozwiązaniu (resolve). Inne kontenery muszą używać gluetun.
  • Kontenery wewnątrz tej samej przestrzeni nazw komunikują się przez localhost.
  • Dwa kontenery w jednej przestrzeni nazw nie mogą nasłuchiwać na tym samym porcie. Dokumentacja gluetun jasno wskazuje: nie ma na to obejścia.
  • Uprawnienia (capabilities) są przypisane do kontenera, a nie do przestrzeni nazw. Gluetun posiada NET_ADMIN oraz /dev/net/tun, ponieważ tworzy interfejs tunelu. Podłączony kontener ich nie dziedziczy.
  • Docker Compose odrzuca pliki, w których jedna usługa definiuje jednocześnie network_mode oraz networks. Należy podłączyć gluetun do sieci, a aplikacja będzie z nich korzystać automatycznie.

Restart gluetun powoduje rozłączenie wszystkich podłączonych do niego elementów. Jest to udokumentowane zachowanie i stanowi powód, dla którego gluetun restartuje proces VPN wewnątrz kontenera zamiast kończyć działanie w przypadku awarii połączenia. Po samodzielnym restarcie lub odtworzeniu gluetun należy zrestartować podłączone do niego kontenery.

Działający plik compose

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

Tag :v3 to najnowsze stabilne wydanie z serii v3. Tag :latest wskazuje na ostatni commit w gałęzi master, która stanowi wersję rozwojową, dlatego należy przypiąć :v3 na maszynie, której nie chcemy debugować we wtorki.

Wartość WEBUI_PORT=8080 musi być zgodna z opublikowanym portem, ponieważ qBittorrent wiąże się wewnątrz przestrzeni nazw gluetun, a reguła publikacji kieruje ruch z hosta na port 8080 w tym kontenerze. Zmiana jednej liczby bez drugiej spowoduje, że port przestanie odpowiadać. Parametr 127.0.0.1:8080:8080 utrzymuje interfejs WWW na adresie pętli zwrotnej hosta. Zwykłe 8080:8080 publikuje usługę na każdym interfejsie i tworzy własną regułę firewalla, co sprawia, że opublikowane porty Docker omijają ufw.

Uruchom usługę, a następnie sprawdź ją w tej kolejności:

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

Polecenie docker compose ps powinno pokazać gluetun jako healthy oraz qbittorrent jako running. Następnie potwierdź adres wyjściowy z wnętrza przestrzeni nazw; jest to test, który decyduje o wszystkim innym:

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

Pole ip w tym pliku JSON powinno zawierać adres Twojego dostawcy VPN. Jeśli jest to adres Twojego serwera, aplikacja nie znajduje się w tunelu i nic poniżej nie będzie działać zgodnie z opisem.

Przechowywanie kluczy poza plikiem compose

gluetun.env przechowuje dane uwierzytelniające i nie jest dodawany do git:

WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32

Obie wartości pochodzą z pliku konfiguracyjnego WireGuard, wygenerowanego w panelu dostawcy. Ustaw uprawnienia pliku na 600. Należy mieć świadomość ograniczeń tego rozwiązania: klucz nie trafia do repozytorium, jednak docker inspect gluetun nadal wyświetla wszystkie zmienne środowiskowe każdemu, kto ma dostęp do gniazda Docker. Pliki środowiskowe i sekrety w Docker Compose opisuje bezpieczniejsze metody.

Komunikacja kontenera spoza tunelu z kontenerem wewnątrz

Oba kierunki są obsługiwane, a każdy z nich wykorzystuje inną nazwę. Kontenery wymagają wspólnej sieci Docker, co w tym przypadku oznacza sieć gluetun, ponieważ dołączony kontener nie posiada własnej. Jak skonfigurowane są sieci Docker Compose opisuje ustawienia domyślne.

Aby połączyć się z zewnątrz do wewnątrz, należy użyć nazwy gluetun oraz portu, na którym nasłuchuje aplikacja. Kontener z reverse proxy uzyskuje dostęp do interfejsu webowego qBittorrent pod adresem gluetun:8080. Wpis ports: nie jest do tego wymagany, ponieważ ruch między kontenerami pozostaje wewnątrz sieci Docker i nie korzysta z portów hosta.

Aby połączyć się z wewnątrz na zewnątrz, należy użyć nazwy usługi drugiego kontenera, na przykład postgres:5432. Gluetun poprawnie rozpoznaje nazwy innych kontenerów wewnątrz swojej przestrzeni nazw od wersji v3.41, dlatego w przypadku problemów z rozpoznawaniem nazw należy przypiąć tę lub nowszą wersję.

Firewall gluetun decyduje o tym, kto może nawiązać z nim połączenie. Ruch z własnej sieci Docker gluetun jest dozwolony. Klient z innej podsieci, laptop w sieci LAN lub kontener w oddzielnej sieci typu bridge, zostanie odrzucony, dopóki nie zostanie zdefiniowana odpowiednia podsieć:

FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

Znaczenie dokumentacji jest precyzyjne: są to oddzielone przecinkami podsieci, do których gluetun oraz kontenery współdzielące jego stos sieciowy mają prawo dostępu.

Połączenia przychodzące z Internetu stanowią odrębny problem. Uczestnicy wymiany (peers) klienta torrent łączą się od strony VPN, więc publikacja portu 6881 na hoście nie przynosi w ich przypadku żadnego efektu. Wymagane jest przekierowanie portu od dostawcy VPN oraz umieszczenie tego portu w FIREWALL_VPN_INPUT_PORTS, co pozwala na obsługę portów od strony serwera VPN. Jest to element, który w większości stosów multimedialnych budowanych za pomocą Docker Compose pozostaje nieskonfigurowany.

Wyłącznik bezpieczeństwa: co dzieje się po zerwaniu tunelu

Ten schemat zyskuje na złożoności w przypadku awarii. Podłączony kontener nie posiada alternatywnej trasy. Jego jedyną drogą wyjścia z maszyny jest współdzielona przestrzeń nazw, więc gdy tunel nie działa, brak jest ścieżki zapasowej. Firewall Gluetun wymusza tę samą zasadę z drugiej strony: ruch wychodzący przechodzi przez tunel lub do punktu końcowego serwera VPN, a cały pozostały ruch jest odrzucany. Nie istnieje okno czasowe, w którym pakiety mogłyby wyciec przez standardowy interfejs podczas ponownego łączenia klienta.

Gluetun monitoruje własne połączenie. Co minutę wysyła żądanie ICMP echo (ping) na adresy w HEALTH_ICMP_TARGET_IPS, które domyślnie wskazują na 1.1.1.1,8.8.8.8. Co pięć minut wykonuje pełne nawiązanie połączenia TCP i TLS (transport layer security) do HEALTH_TARGET_ADDRESSES, domyślnie cloudflare.com:443,github.com:443. Gdy te próby kończą się niepowodzeniem, restartuje VPN wewnątrz kontenera i odnotowuje to w logach:

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

Analizując logi podłączonego kontenera, należy pamiętać o tej kolejności. Wiersze takie jak connection refused, operation not permitted oraz i/o timeout wewnątrz aplikacji są skutkami martwego tunelu, a nie jego przyczynami. Dokumentacja Gluetun wyraźnie o tym ostrzega, ponieważ użytkownicy często zgłaszają skutki i poświęcają godziny na ich bezowocną analizę.

HEALTH_RESTART_VPN=on jest ustawieniem domyślnym i powinno pozostać włączone. Należy je wyłączać wyłącznie podczas debugowania konkretnej awarii, ponieważ bez niego martwy tunel pozostaje w stanie nieaktywnym.

Kolejność: zatrzymanie uruchamiania stosu przed nawiązaniem tunelu

Obraz zawiera wbudowany mechanizm Docker healthcheck:

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

To polecenie uruchamia drugą, krótkotrwałą kopię gluetun, która odpytuje serwer stanu działającej instancji pod adresem http://127.0.0.1:9999/. Poprawnie działający tunel zwraca 200 OK. Zerwane połączenie zwraca 500 Internal server error wraz z komunikatem błędu, a kontener zostaje oznaczony jako niezdrowy po pojedynczej awarii.

condition: service_healthy odpowiada za oczekiwanie na ten stan. Zwykłe depends_on: [gluetun] czeka jedynie na uruchomienie kontenera, co następuje kilka sekund przed zakończeniem procesu uzgadniania połączenia. W efekcie aplikacja startuje w martwej sieci i często przerywa działanie już przy pierwszej próbie połączenia. Healthchecki w Docker Compose szczegółowo omawia składnię oraz pola określające czas oczekiwania.

Istnieje jedno ograniczenie, które często sprawia trudności. Compose ocenia ten warunek tylko raz, w momencie tworzenia kontenera. Nie zatrzymuje ani nie restartuje aplikacji w późniejszym czasie, jeśli gluetun przejdzie w stan niezdrowy. W takim przypadku działa wewnętrzny mechanizm samonaprawy gluetun, który restartuje proces VPN zamiast całego kontenera.

Sprawdź wyciek DNS przed zaufaniem konfiguracji

DNS (Domain Name System) to wyciek, który może wystąpić nawet przy poprawnie działającym tunelu. Gluetun uruchamia własny resolver wewnątrz przestrzeni nazw i domyślnie przesyła zapytania przez DoT (DNS over TLS) do Cloudflare: DNS_UPSTREAM_RESOLVER_TYPE=dot oraz DNS_UPSTREAM_RESOLVERS=cloudflare. Pozostawienie tych ustawień bez zmian zapewnia szyfrowanie zapytań i ich przesyłanie przez tunel.

Ustawieniem, które powoduje ten problem, jest DNS_UPSTREAM_PLAIN_ADDRESSES. Użytkownicy zmieniają je, gdy nazwy nie są poprawnie rozwiązywane i chcą, aby odpowiedzi udzielał router lub resolver dostawcy usług internetowych. Dokumentacja Gluetun jasno określa konsekwencje: cały ruch DNS nie będzie przechodził przez tunel VPN i wycieknie na zewnątrz. Ruch sieciowy pozostaje prywatny, ale lista odwiedzanych nazw hostów – nie. Wersja tego samego błędu dla WireGuard została omówiona w DNS, który przestaje działać przez tunel WireGuard.

Aby przeprowadzić test, ustaw HTTPPROXY=on w gluetun i opublikuj 8888:8888/tcp, a następnie skieruj przeglądarkę na to proxy i uruchom test wycieku DNS. Wynik powinien wskazywać dostawcę usług lub Cloudflare, a nigdy domowy router. Dokumentacja Gluetun ostrzega, że niektóre testy wycieków mogą podawać nietypowe wyniki, ponieważ resolver wewnątrz przestrzeni nazw jest lokalnym pośrednikiem buforującym, a nie serwerem, który udziela ostatecznej odpowiedzi. Wskazanie niewłaściwego kraju lub własnego resolvera ISP należy traktować jako sygnał ostrzegawczy.

Dodawanie Tailscale obok sidecara VPN i kwestia pierwszeństwa

Tailscale to sieć nakładkowa oparta na WireGuard, służąca do łączenia własnych maszyn. Użytkownicy często uruchamiają ją obok VPN dostawcy, aby zachować ścieżkę administracyjną do stosu usług. Oba rozwiązania rzadko wchodzą w konflikt, co wynika z konkretnej zasady działania. Dokumentacja Tailscale określa domyślne zachowanie: usługa działa jako sieć nakładkowa, kieruje ruch wyłącznie między urządzeniami z uruchomionym Tailscale i nie ingeruje w publiczny ruch internetowy.

Odpowiedź zależy zatem od jednego ustawienia.

  • Tailscale we własnym kontenerze, konfiguracja domyślna: usługa nie widzi ruchu wychodzącego aplikacji. Cały ruch obsługuje Gluetun. Tailscale łączy się z aplikacją pod adresem gluetun:8080, dokładnie tak samo jak każdy inny zewnętrzny kontener.
  • Tailscale podłączony do przestrzeni nazw (namespace) Gluetun za pomocą network_mode: "service:gluetun": wymaga własnych cap_add w postaci net_admin oraz net_raw, ponieważ uprawnienia nie są dziedziczone wraz z przestrzenią nazw. W domyślnym trybie sieciowym userspace, gdy TS_USERSPACE jest włączone, tailscaled nie tworzy żadnego interfejsu i działa jako proxy SOCKS5 lub HTTP, więc nie może modyfikować routingu. Gluetun nadal obsługuje cały ruch.
  • Ten sam przypadek z TS_USERSPACE=false: tailscaled tworzy urządzenie tunelowe i dodaje trasy, ale tylko dla zakresu sieci tailnet 100.64.0.0/10 oraz tras podsieci rozgłaszanych za pomocą TS_ROUTES. Ruch publiczny nadal wychodzi przez Gluetun.
  • Każdy z powyższych przypadków z wybranym węzłem wyjściowym (exit node), sudo tailscale set --exit-node=<exit-node-ip>: Tailscale przejmuje domyślną trasę i zyskuje pierwszeństwo. Nie należy łączyć tego z Gluetun. Może istnieć tylko jedna domyślna trasa i jeden jej zarządca.

Efektem ubocznym jest sytuacja, w której Tailscale działa wewnątrz tunelu. Węzły sieci widzą adres dostawcy VPN, więc należy spodziewać się częstszego korzystania z przekaźników (relays). tailscale status wyświetla relay "..." obok węzła zamiast direct, gdy taka sytuacja wystąpi. Połączenie działa, ale jest wolniejsze. Jeśli sieć nakładkowa jest jedynym potrzebnym rozwiązaniem, różnica między zwykłym WireGuard a Tailscale stanowi lepszy punkt wyjścia.

Co ulega awarii i jaki komunikat zobaczysz

Docker odmawia utworzenia kontenera aplikacji. Error response from daemon: conflicting options: port publishing and the container type network mode oznacza, że blok ports: nadal znajduje się w przypisanej usłudze. Przenieś go do gluetun.

Compose odrzuca cały plik. Usługa nie może jednocześnie ustawić network_mode oraz networks. Umieść sieci w gluetun.

Inny kontener nie może rozwiązać nazwy aplikacji. curl: (6) Could not resolve host: qbittorrent to poprawne zachowanie, ponieważ przypisany kontener nie dołączył do żadnej sieci i nie zarejestrował nazwy. Użyj gluetun oraz portu.

Drugi przypisany kontener nie uruchamia się. Dwa procesy w tej samej przestrzeni nazw nie mogą powiązać tego samego portu, a proces, który przegrał, zgłasza błąd, że adres jest już w użyciu. Zmień port wewnętrzny aplikacji lub uruchom drugą instancję gluetun.

Aplikacja nie ma sieci po modyfikacji gluetun. Restartowanie lub odtwarzanie gluetun przerywa łączność dla wszystkich przypisanych do niego kontenerów. Zrestartuj te kontenery.

Małe strony ładują się, a duże zawieszają. Przyczyną jest MTU (maximum transmission unit). Tunel dodaje narzut, a któryś z elementów na ścieżce odrzuca zbyt duże pakiety bez wysyłania informacji zwrotnej o błędzie. Zmniejsz WIREGUARD_MTU, spróbuj 1400, a następnie 1320.

Gluetun nie przechodzi w stan healthy. Kontrola startowa wskazuje pierwszych podejrzanych: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. Sprawdź, czy klucz nie wygasł, czy lista serwerów nie jest przestarzała oraz czy firewall hosta nie blokuje ruchu wychodzącego UDP.

FAQ

Dlaczego opublikowane porty kontenera przestały działać za Gluetun?

Ponieważ network_mode: "service:gluetun" umieszcza kontener wewnątrz przestrzeni nazw sieciowej (network namespace) Gluetun, a przestrzeń nazw posiada jeden adres IP i jeden zestaw portów nasłuchujących. Aplikacja nadal nasłuchuje, ale reguła publikacji musi znajdować się w kontenerze, który jest właścicielem przestrzeni nazw. Przenieś listę ports: do usługi gluetun. Jeśli pozostawisz ją w podpiętej usłudze, Docker nawet jej nie utworzy: Error response from daemon: conflicting options: port publishing and the container type network mode.

Jak uzyskać dostęp do kontenera wewnątrz tunelu VPN z kontenera znajdującego się poza nim?

Użyj nazwy usługi gluetun oraz portu, na którym nasłuchuje aplikacja, na przykład gluetun:8080. Podpięty kontener nie posiada własnej sieci Docker, więc jego nazwa nie zostanie rozpoznana. Ruch między kontenerami nie wymaga publikowania portów. W drugą stronę, kontener wewnątrz przestrzeni nazw łączy się z zewnętrznym kontenerem poprzez jego nazwę usługi, taką jak postgres:5432, w wersji Gluetun v3.41 i nowszych. Klient z innej podsieci, na przykład laptop w sieci lokalnej, jest blokowany przez zaporę sieciową gluetun, dopóki nie dodasz tej podsieci do FIREWALL_OUTBOUND_SUBNETS.

Czy Gluetun działa jako kill switch, gdy połączenie VPN zostanie zerwane?

Tak, z dwóch powodów jednocześnie. Podpięty kontener nie posiada żadnej trasy poza tą w udostępnionej przestrzeni nazw, więc martwy tunel pozostawia go bez ścieżki wyjścia z maszyny. Zapora sieciowa Gluetun zezwala również na ruch wychodzący tylko przez tunel oraz do punktu końcowego serwera VPN. Gluetun następnie restartuje VPN wewnętrznie, logując WARN [vpn] restarting VPN because it failed to pass the healthcheck, zamiast kończyć działanie, ponieważ każdy podpięty kontener traci sieć w momencie restartu samego gluetun.

Tailscale i Gluetun w tym samym stosie: który z nich obsługuje ruch wychodzący?

Gluetun, w każdej konfiguracji poza jedną. Tailscale domyślnie kieruje ruch tylko między urządzeniami w Twojej sieci tailnet i nie ingeruje w ruch publiczny. W domyślnym trybie userspace obrazu kontenera nie tworzy on żadnego interfejsu, więc nie wpływa na routing. Z flagą TS_USERSPACE=false instaluje trasy tylko dla 100.64.0.0/10 oraz Twoich anonsowanych podsieci. Wyjątkiem jest exit node: sudo tailscale set --exit-node=<exit-node-ip> sprawia, że Tailscale staje się domyślną trasą i wtedy to on przejmuje kontrolę. Wybierz jeden produkt, który będzie zarządzał domyślną trasą, zamiast łączyć oba rozwiązania.