SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-28

Gluetun: raggiungere host e altri container

Con Gluetun, il container condivide il namespace di rete: pubblica le porte su Gluetun e consenti solo le subnet necessarie fuori dal tunnel.

Cosa succede quando un container entra nella rete di gluetun

Un container che imposta network_mode: service:gluetun non dispone di interfacce di rete proprie. Entra nel namespace di rete di gluetun, quindi la pubblicazione delle porte e le regole del firewall non sono più proprietà di quel container, ma del servizio gluetun. Tutte le risposte riportate di seguito derivano da questo unico fatto.

Un namespace di rete è una copia privata dello stack di rete del kernel: dispone di interfacce, tabella di routing, regole del firewall e socket in ascolto propri. Docker ne assegna uno a ogni container per impostazione predefinita. Quando si specifica network_mode: service:gluetun, Docker salta questo passaggio e inserisce il nuovo container nel namespace già posseduto da gluetun. Il container mantiene il proprio filesystem e il proprio file /etc/hosts, e questo secondo aspetto sarà importante più avanti.

È possibile verificarlo direttamente.

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

Il comando stampa container: seguito dall'ID del container gluetun, mentre per un container normale stamperebbe bridge. Questa guida riprende il punto in cui si interrompe instradare il traffico Docker attraverso una VPN con gluetun: il tunnel funziona, ma ora nessuno riesce a comunicare con il container.

Pubblica la porta su gluetun, non sull'applicazione

Lasciare un blocco ports: sul servizio che imposta network_mode impedisce a Docker di creare il container:

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

Il motivo è diretto. Pubblicare una porta significa aggiungere una regola NAT (network address translation) che inoltra una porta dell'host nello spazio dei nomi di rete del container. Questo container non dispone di uno spazio dei nomi di rete proprio. Sposta il mapping sul servizio gluetun. Il numero della porta non cambia, perché l'applicazione continua ad ascoltare su quella porta nello spazio dei nomi condiviso.

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

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

Un blocco expose: sul servizio dipendente è altrettanto inutile, mentre un blocco networks: su quel servizio blocca completamente l'avvio: Compose segnala che il servizio dichiara network_mode e networks, che sono mutuamente esclusivi, e rifiuta di caricare il file.

Una conseguenza si manifesta in seguito. Tutti i container nello spazio dei nomi condividono un unico spazio delle porte. Di conseguenza, due applicazioni che usano entrambe 8080 come valore predefinito entrano in conflitto e la seconda ad avviarsi termina con un errore che indica che l'indirizzo è già in uso. Modifica una delle due applicazioni nella relativa configurazione, ad esempio la variabile WEBUI_PORT nell'immagine LinuxServer qBittorrent, quindi pubblica il nuovo numero su gluetun.

Come comunicano tra loro i container dietro gluetun?

All'interno del namespace condividono già un'interfaccia loopback. Un container dietro gluetun raggiunge il container associato a 127.0.0.1:<port> senza usare alcuna rete Docker.

Dall'esterno del namespace, il container non ha un nome. Il DNS integrato di Docker risolve il nome di un servizio nell'indirizzo di quel servizio su una rete definita dall'utente, ma questo container non ha un indirizzo su alcuna rete. Di conseguenza, un normale container come Sonarr non raggiunge il client torrent all'indirizzo http://qbittorrent:8080. Lo raggiunge all'indirizzo http://gluetun:8080, perché il socket è in ascolto nel namespace di gluetun, sull'indirizzo di gluetun. Questo comportamento sorprende chi sa come funzionano le reti e i nomi dei servizi in Docker Compose e si aspetta che venga applicata la normale risoluzione dei nomi. Funziona anche senza pubblicare nulla sull'host, perché entrambi i container si trovano sulla stessa rete Compose.

Controlla il DNS prima di eseguire qualsiasi altro debug. Gluetun usa il proprio resolver e riscrive /etc/resolv.conf nel proprio container, ma /etc/resolv.conf è un file specifico per ogni container, quindi quello scritto da gluetun non è quello letto dalla tua applicazione.

docker exec qbittorrent cat /etc/resolv.conf

Come raggiungere un servizio in esecuzione sull'host Docker?

Usare host.docker.internal. Richiede due impostazioni in due punti diversi, perché ci sono due problemi distinti.

Prima si configura il nome. /etc/hosts è specifico del container, quindi la voce extra_hosts appartiene al container dell'applicazione, non a gluetun.

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

host-gateway è un valore speciale che Docker sostituisce con un indirizzo interno dell'host stesso. In una normale installazione Docker su Linux, corrisponde all'indirizzo del bridge docker0, comunemente 172.17.0.1. Verificare il valore in uso con ip -4 addr show docker0 sul VPS. Docker Desktop risolve autonomamente questo nome. Per questo le guide scritte per un laptop omettono la riga extra_hosts, mentre lo stesso file poi non funziona su un server.

Poi si configura il percorso. Aggiungere il nome indica soltanto al container quale indirizzo usare. Il pacchetto continua a uscire tramite il percorso predefinito di gluetun, cioè il tunnel, e il firewall di gluetun lo scarta. Il sintomo è una connessione che resta in attesa e infine va in timeout, non una connessione rifiutata. Un rifiuto indica che il pacchetto è arrivato e che qualcosa ha risposto negativamente. Un timeout indica che il pacchetto non è mai arrivato.

  gluetun:
    environment:
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32

Verificare quindi che il servizio sull'host sia effettivamente in ascolto su quell'indirizzo. Un server PostgreSQL associato soltanto a 127.0.0.1 non è raggiungibile da alcun container, indipendentemente dalla presenza del tunnel, perché 127.0.0.1 all'interno del namespace è il loopback del namespace stesso. Associarlo invece a 172.17.0.1: accetta connessioni dai container senza restare esposto sull'interfaccia pubblica. Verificare con ss -lntp | grep 5432 sull'host.

Che cosa modifica realmente FIREWALL_OUTBOUND_SUBNETS

La documentazione di gluetun descrive questo parametro come l’elenco, separato da virgole, delle subnet che gluetun e i container che condividono il suo network stack possono raggiungere. Specifica inoltre che il parametro modifica il firewall e il routing. Entrambi gli aspetti sono importanti. Gluetun aggiunge una route per ogni subnet indicata tramite il gateway del bridge Docker, quindi i pacchetti destinati a quegli indirizzi escono tramite eth0 invece di passare dal tunnel. Inoltre, apre il firewall per tali subnet, perché altrimenti gluetun scarta il traffico in uscita che non è diretto al server VPN.

Inserisci il valore senza spazi dopo le virgole.

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

Due proprietà sono facili da trascurare. Questa è un’impostazione a livello di namespace, quindi si applica a ogni container che si trova dietro gluetun, non soltanto a quello considerato. Inoltre, riguarda esclusivamente il traffico in uscita: controlla le connessioni avviate da un container. Le connessioni in ingresso a una porta pubblicata seguono un percorso diverso e non richiedono alcuna voce in questo parametro.

Accesso all’interfaccia web da un peer Tailscale

Tailscale assegna a ogni macchina un indirizzo nell'intervallo 100.64.0.0/10, riservato al carrier-grade NAT. Su una tailnet personale tutto questo non ha costi, anche se i limiti di utenti e dispositivi del piano gratuito determinano se la situazione resta invariata quando altre persone devono accedere alle stesse interfacce. Poiché la fatturazione si basa sugli utenti e non sulle macchine, il costo effettivo di una tailnet a pagamento dipende dal numero di persone invitate, non dal numero di container che rendi loro accessibili. Le due direzioni richiedono attività diverse.

Il traffico in ingresso è il caso più semplice. La pubblicazione di 8080:8080 su gluetun associa quella porta a tutti gli indirizzi dell’host. L’interfaccia tailscale0 dell’host è uno di questi indirizzi, quindi un peer apre http://<machine-name>:8080 e raggiunge il container. Gluetun non interviene in questo percorso, perché la regola NAT di Docker si trova sull’host, al di fuori del namespace.

Per rendere l’interfaccia accessibile soltanto tramite la tailnet, associa la porta pubblicata all’indirizzo Tailscale dell’host invece che a tutti gli indirizzi.

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

Trova questo indirizzo con tailscale ip -4 sull’host. In questo caso l’associazione è un controllo più efficace di una regola firewall, perché la porta non viene mai aperta sull’interfaccia pubblica. Se preferisci raggiungere l’interfaccia tramite un nome HTTPS invece che tramite host e porta, tailscale serve può fungere da proxy per quella porta pubblicata. Tuttavia, serve e funnel differiscono per chi può raggiungerla e solo uno dei due mantiene l’interfaccia all’interno della tailnet. Questo evita anche il problema descritto in Docker pubblica le porte direttamente oltre ufw.

Il traffico in uscita è il punto in cui torna in gioco FIREWALL_OUTBOUND_SUBNETS. Se un container deve chiamare un peer, aggiungi l’indirizzo di quel peer e preferisci un /32 per peer invece dell’intero /10. Se la macchina da contattare si trova su una rete privata raggiunta tramite un VPS che annuncia quella subnet alla tailnet, indica l’intervallo annunciato invece dell’indirizzo 100.x del router e verifica che l’host stesso abbia accettato quelle route. I nomi MagicDNS non vengono risolti all’interno del container, perché il container non usa il resolver dell’host. Usa quindi l’indirizzo numerico 100.x oppure fissalo con una riga extra_hosts. Lo stesso vale quando esegui un tuo server di controllo Tailscale con Headscale.

Un file Compose completo per la struttura più comune

Un client per il download dietro la VPN, 2 interfacce Web accessibili solo sulla tailnet e un container che legge un database PostgreSQL in esecuzione sull'host.

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

Leggete il file per capire la struttura, non per i nomi dei prodotti. Entrambe le interfacce Web sono pubblicate su gluetun e associate all'indirizzo tailnet dell'host, quindi rispondono su Tailscale e in nessun altro punto. Solo Prowlarr contiene la riga extra_hosts, perché è il container Prowlarr a risolvere host.docker.internal. FIREWALL_OUTBOUND_SUBNETS definisce 2 indirizzi singoli: l'indirizzo del bridge Docker dell'host, che consente a Prowlarr di aprire una connessione al database, e un peer della tailnet.

Il server PostgreSQL è volutamente assente dal file. È in esecuzione sul VPS come normale servizio di sistema e resta in ascolto su 172.17.0.1:5432. È lo stesso livello architetturale di uno stack arr su Docker Compose, con il database spostato all'esterno di Docker.

Tenete la chiave privata WireGuard fuori dal file Compose. ${WIREGUARD_PRIVATE_KEY} legge i dati da un file .env affiancato al file Compose; questo è il modello descritto in file env e secret per Docker Compose. La clausola condition: service_healthy usa l'healthcheck già incluso nell'immagine gluetun, quindi nessun servizio viene avviato finché il tunnel non segnala di essere attivo. Gli healthcheck di Compose descrivono la struttura generale.

Pubblicazione su ogni indirizzo invece che solo sulla tailnet

Rimuovete il prefisso dell'indirizzo e i binding delle porte su 0.0.0.0, che include l'IP pubblico del VPS. Eseguite questa modifica solo dietro un firewall sotto il vostro controllo e leggete prima la nota su ufw riportata sopra.

    ports:
      - "8080:8080/tcp"

Confermare che il tunnel stia ancora trasportando traffico

Eseguire la stessa richiesta due volte, una volta dall'interno del namespace e una volta dall'host, quindi confrontare i risultati.

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

Il primo comando dovrebbe mostrare l'indirizzo di uscita del provider VPN. Il secondo dovrebbe mostrare l'indirizzo del VPS. Se coincidono, il traffico del container non passa attraverso il tunnel. Fino a quando il problema non viene corretto, le altre procedure di questa guida non sono pertinenti.

La tabella di routing mostra quale traffico passa attraverso il tunnel e quale lo evita.

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

La route predefinita dovrebbe puntare all'interfaccia del tunnel, tun0. Sotto di essa dovresti vedere una route per ogni voce di FIREWALL_OUTBOUND_SUBNETS, con destinazione il gateway del bridge Docker. Qualsiasi altra route che passa da eth0 identifica traffico che bypassa la VPN.

Il control server di Gluetun riporta lo stesso IP pubblico sulla porta 8000, all'indirizzo /v1/publicip/ip. Le versioni recenti richiedono la configurazione dell'autenticazione per le route del control server. Configurala prima di farvi affidamento.

La perdita di una subnet errata crea

FIREWALL_OUTBOUND_SUBNETS è un'apertura intenzionale nel firewall, quindi la dimensione dell'apertura corrisponde alla dimensione del rischio. Ecco quattro modi per renderla troppo ampia:

  • 0.0.0.0/0 invia tutto il traffico all'esterno del tunnel. I due controlli dell'indirizzo IP precedenti lo rilevano alla prima esecuzione, perché restituiscono lo stesso indirizzo.
  • Un intervallo più ampio del necessario. Aprire 10.0.0.0/8 per raggiungere una macchina a 10.0.1.7 espone anche ogni indirizzo che un peer torrent potrebbe annunciare in quell'intervallo. Scrivere 10.0.1.7/32.
  • Un intervallo che si sovrappone agli indirizzi usati dal tunnel. La documentazione di gluetun avverte che, in questo caso, gluetun invia invece il traffico VPN attraverso il bridge, interrompendo il port forwarding. Controllare il valore WIREGUARD_ADDRESSES prima di aprire qualsiasi intervallo privato.
  • 100.64.0.0/10 per Tailscale. In questo modo si aprono circa quattro milioni di indirizzi per raggiungere un solo peer. Elencare i peer necessari come voci /32.

Ricordare che l'impostazione riguarda l'intero namespace. Aprire una subnet affinché un indexer possa raggiungere un servizio sull'host apre la stessa subnet anche per il client torrent che condivide quel namespace. Eseguire nuovamente il controllo dell'IP pubblico dopo ogni modifica a questa variabile, perché è l'unico test che mostra se la modifica ha prodotto l'effetto previsto.

Cosa si rompe quando riavvii gluetun

Gluetun gestisce il namespace, quindi il ciclo di vita di gluetun coincide con quello del namespace. L'avvio di un container dipendente mentre gluetun è arrestato fallisce immediatamente:

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

Il riavvio di gluetun senza ricreare il gruppo causa un errore meno evidente. I container dipendenti continuano a essere eseguiti mentre il namespace a cui erano collegati viene ricreato sotto di loro, quindi docker ps segnala che tutto è operativo, ma nessun servizio risponde. Dopo ogni modifica al servizio gluetun, ricrea l'intero gruppo invece di riavviare un solo componente.

docker compose up -d --force-recreate

Lo stesso vale per gli aggiornamenti dell'immagine. Se scarichi una nuova immagine di gluetun e ricrei solo quel servizio, gli altri container continueranno a puntare a un namespace che non esiste più.

FAQ

Perché Docker restituisce "port publishing and the container type network mode"?

Perché un blocco ports: è ancora presente in un servizio che imposta anche network_mode: service:gluetun. La pubblicazione di una porta aggiunge una regola NAT che inoltra una porta dell'host allo spazio dei nomi di rete del container. Un container in questa modalità non dispone di uno spazio dei nomi di rete proprio. Elimina il blocco ports: da quel servizio e aggiungi la stessa mappatura al servizio gluetun. Il numero di porta resta invariato, perché l'applicazione continua ad ascoltare su quella porta nello spazio dei nomi condiviso.

Come possono raggiungere un servizio dietro gluetun gli altri container?

I container che si trovano nello stesso spazio dei nomi si raggiungono tramite 127.0.0.1. I container esterni usano il nome del servizio gluetun. Di conseguenza, http://gluetun:8080 funziona mentre http://qbittorrent:8080 non funziona. Il container dell'applicazione non ha un indirizzo su alcuna rete Docker, quindi il server DNS integrato non ha alcun indirizzo da risolvere per il suo nome. Non è necessaria la pubblicazione di una porta, purché entrambi i container condividano una rete Compose.

Cosa devo inserire in FIREWALL_OUTBOUND_SUBNETS?

Inserisci soltanto gli indirizzi verso cui un container dietro gluetun deve avviare una connessione, usando il prefisso più restrittivo possibile. Una singola macchina è un /32. Le due voci più comuni sono l'host Docker, indicato da 172.17.0.1/32, e un /32 per ogni peer Tailscale che contatti. Non aggiungere mai 0.0.0.0/0 e non aggiungere mai un intervallo che si sovrapponga agli indirizzi del tunnel VPN. Le connessioni in ingresso verso una porta pubblicata non richiedono alcuna voce.

Perché il container non riesce a risolvere i nomi MagicDNS di Tailscale?

MagicDNS funziona configurando il resolver dell'host affinché utilizzi il server DNS di Tailscale. Il container non usa però il resolver dell'host. Usa i server indicati dal proprio /etc/resolv.conf, che dietro gluetun corrispondono alla configurazione DNS di gluetun. Verifica con docker exec <container> cat /etc/resolv.conf. Usa l'indirizzo numerico 100.x del peer oppure associa il nome a un indirizzo con una voce extra_hosts nel container.

Come posso verificare che il traffico passi ancora attraverso la VPN?

Esegui una richiesta dallo spazio dei nomi e la stessa richiesta dall'host, quindi confronta le risposte. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org dovrebbe restituire l'indirizzo di uscita del provider VPN, mentre curl -s https://api.ipify.org eseguito sul VPS restituisce l'indirizzo del VPS. Se le due risposte coincidono, il tunnel non sta trasportando il traffico del container. Ripeti questo controllo dopo ogni modifica a FIREWALL_OUTBOUND_SUBNETS.