SSD Nodes Learn 🎉 VPS da $4.99/mese
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-05

Instradare container Docker tramite VPN con Gluetun

Le porte del container scompaiono se usi network_mode service:gluetun? Scopri perché il namespace di rete condiviso causa questo errore e come configurare il file compose.

Perché le porte scompaiono quando si instrada un container Docker attraverso una VPN

Per instradare i container Docker attraverso una VPN, si assegna il tunnel a un container e si collegano gli altri al suo namespace di rete tramite network_mode: "service:gluetun". Questo collegamento è l'aspetto che solitamente sorprende gli utenti. Il container collegato non possiede più una rete propria, pertanto le sue porte pubblicate e il suo nome di servizio Docker cessano di esistere. È necessario pubblicare le porte sul container VPN; gli altri container raggiungeranno l'applicazione utilizzando il nome del container VPN.

Se si lascia un blocco ports: nel container collegato, Docker rifiuterà di crearlo:

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

Lo strumento utilizzato in questo caso è Gluetun, un container che si connette a un provider VPN (virtual private network) commerciale tramite WireGuard o OpenVPN e integra un proprio firewall. La release v3.41.3 è quella corrente ad agosto 2026. Gli esempi utilizzano Mullvad con WireGuard, pertanto sono necessari un account e una chiave forniti dal proprio provider. Se si preferisce terminare il tunnel su hardware di proprietà, eseguire il proprio server WireGuard su una VPS permette di configurare l'altro capo della connessione, mentre wg-easy in Docker fornisce un'interfaccia web per la gestione.

Cosa fa realmente network_mode: "service:gluetun"

Ogni container Docker ottiene normalmente il proprio namespace di rete: le proprie interfacce, tabella di routing, regole firewall e socket in ascolto. La modalità service: salta questo passaggio e avvia il container all'interno del namespace di gluetun. Un unico namespace significa un unico indirizzo IP, il che comporta sei conseguenze.

  • L'applicazione non ha un proprio indirizzo. Il suo indirizzo è quello di gluetun.
  • L'applicazione non è collegata a nessuna rete Docker, quindi il suo nome di servizio non viene mai registrato e non viene mai risolto. Gli altri container devono usare gluetun.
  • I container all'interno del namespace comunicano tra loro tramite localhost.
  • Due container nello stesso namespace non possono essere in ascolto sulla stessa porta. La documentazione di gluetun è chiara a riguardo: non esiste alcuna soluzione alternativa.
  • Le capability appartengono a un container, non a un namespace. Gluetun detiene NET_ADMIN e /dev/net/tun perché crea l'interfaccia tunnel. Il container collegato non le eredita.
  • Compose rifiuta qualsiasi file in cui un servizio imposta sia network_mode che networks. Collega gluetun alle tue reti e l'applicazione ne seguirà il percorso.

Il riavvio di gluetun disconnette tutto ciò che vi è collegato. Si tratta di un comportamento documentato ed è il motivo per cui gluetun riavvia il processo VPN all'interno del container invece di terminare quando la connessione fallisce. Dopo aver riavviato o ricreato manualmente gluetun, riavvia i container ad esso collegati.

Il file compose funzionante

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

Il tag :v3 è l'ultima release stabile della serie v3. Il tag :latest punta all'ultimo commit del branch master, ovvero la versione di sviluppo; pertanto, non utilizzare :v3 su una macchina che non si desidera dover sottoporre a debug di martedì.

WEBUI_PORT=8080 deve corrispondere alla porta pubblicata, poiché qBittorrent si collega all'interno del namespace di gluetun e la regola di pubblicazione invia il traffico dell'host verso la porta 8080 in tale ambiente. Se si modifica un numero senza l'altro, la porta non risponderà ad alcuna richiesta. 127.0.0.1:8080:8080 mantiene l'interfaccia web sull'indirizzo di loopback dell'host. Una configurazione 8080:8080 semplice pubblica su ogni interfaccia e scrive la propria regola firewall, motivo per cui le porte pubblicate da Docker ignorano ufw.

Avvia il servizio, quindi esegui le verifiche in questo ordine:

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

docker compose ps dovrebbe mostrare gluetun come healthy e qbittorrent come running. Successivamente, conferma l'indirizzo di uscita dall'interno del namespace; questo è il controllo che determina il funzionamento di tutto il resto:

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

Il campo ip in quel JSON dovrebbe corrispondere all'indirizzo del tuo provider VPN. Se visualizzi l'indirizzo del tuo server, l'applicazione non è nel tunnel e nulla di quanto descritto di seguito funzionerà come previsto.

Mantenere le chiavi fuori dal file compose

gluetun.env contiene le credenziali e deve rimanere fuori da git:

WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32

Entrambi i valori provengono da un file di configurazione WireGuard generato nell'area account del proprio provider. Impostare il file con permessi 600. È necessario essere chiari sui vantaggi reali: la chiave rimane fuori dal repository, ma docker inspect gluetun stampa comunque ogni variabile d'ambiente a chiunque possa accedere al socket Docker. File di ambiente e secret in Docker Compose illustra le opzioni più sicure.

Come un container esterno al tunnel comunica con uno interno

Entrambe le direzioni funzionano e ognuna utilizza un nome diverso. I due container devono condividere una rete Docker, ovvero la rete di gluetun, dato che il container collegato non ne possiede una propria. Come sono cablate le reti Docker Compose copre le impostazioni predefinite.

Dall'esterno verso l'interno, usa il nome di gluetun e la porta su cui l'applicazione è in ascolto. Un container reverse proxy raggiunge l'interfaccia web di qBittorrent all'indirizzo gluetun:8080. Non è necessaria alcuna voce ports: per questo, poiché il traffico tra container rimane all'interno della rete Docker e non tocca mai una porta dell'host.

Dall'interno verso l'esterno, usa il nome del servizio dell'altro container, ad esempio postgres:5432. Gluetun risolve i nomi degli altri container dal proprio namespace a partire dalla versione v3.41; quindi, blocca quella versione o una successiva se un nome si rifiuta di essere risolto.

Il firewall di gluetun decide chi può aprire una connessione verso di esso. Il traffico proveniente dalla rete Docker di gluetun è consentito. Un client su una subnet diversa, un portatile sulla tua LAN o un container su una rete bridge separata, viene bloccato finché non specifichi quella subnet:

FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

Il significato documentato è preciso: subnet separate da virgole a cui gluetun e i container che condividono il suo stack di rete possono accedere.

Le connessioni in ingresso da Internet rappresentano un problema distinto. I peer di un client torrent arrivano dal lato VPN, quindi pubblicare la porta 6881 sull'host non serve a nulla per loro. È necessaria una porta inoltrata dal proprio provider e tale porta deve essere elencata in FIREWALL_VPN_INPUT_PORTS, che consente le porte dal lato del server VPN. Questo è l'elemento che la maggior parte degli stack multimediali creati con Docker Compose lascia non configurato.

Il kill switch: cosa succede quando il tunnel cade

Questo schema giustifica la sua complessità in caso di errore. Il container collegato non dispone di una seconda rotta. Il suo unico percorso verso l'esterno è il namespace condiviso, quindi quando il tunnel è inattivo non esiste alcuna alternativa. Il firewall di Gluetun applica la stessa regola dal lato opposto: il traffico in uscita transita attraverso il tunnel o verso l'endpoint del server VPN, mentre tutto il resto viene scartato. Non esiste alcun intervallo in cui i pacchetti possano fuoriuscire dall'interfaccia standard durante la riconnessione di un client.

Gluetun monitora la propria connessione. Ogni minuto invia un echo ICMP (un ping) agli indirizzi in HEALTH_ICMP_TARGET_IPS, che per impostazione predefinita sono 1.1.1.1,8.8.8.8. Ogni cinque minuti esegue un dial TCP e TLS (transport layer security) completo verso HEALTH_TARGET_ADDRESSES, predefinito cloudflare.com:443,github.com:443. Quando questi tentativi falliscono, riavvia la VPN all'interno del container e registra l'evento:

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

Leggi i log del container collegato tenendo a mente questo ordine. Righe come connection refused, operation not permitted e i/o timeout all'interno dell'applicazione sono conseguenze di un tunnel non attivo, non cause. La documentazione di Gluetun lo afferma esplicitamente, poiché gli utenti tendono a segnalare la conseguenza e a cercarne la causa per ore.

HEALTH_RESTART_VPN=on è l'impostazione predefinita e dovrebbe rimanere attiva. Disabilitala solo durante il debug di un errore specifico, poiché con questa opzione disattivata un tunnel non attivo rimane tale.

Ordinamento: impedire l'avvio dello stack prima che il tunnel sia attivo

L'immagine include un healthcheck di Docker:

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

Questo comando esegue una seconda istanza temporanea di gluetun che interroga il server di stato di quella in esecuzione su http://127.0.0.1:9999/. Un tunnel funzionante risponde 200 OK. Uno non funzionante risponde 500 Internal server error con una stringa di errore e il container viene contrassegnato come non integro dopo un singolo fallimento.

condition: service_healthy è ciò che attende questo stato. Il semplice depends_on: [gluetun] attende solo l'avvio del container, che avviene diversi secondi prima del completamento dell'handshake; di conseguenza, l'applicazione si avvia su una rete non attiva e spesso interrompe il tentativo di connessione iniziale. Healthcheck in Docker Compose esamina la sintassi e i campi relativi alla temporizzazione.

Un limite spesso causa problemi. Compose valuta tale condizione una sola volta, al momento della creazione del container. Non arresta né riavvia l'applicazione in un secondo momento se gluetun diventa non integro. L'auto-ripristino interno di gluetun gestisce questo caso, motivo per cui riavvia il processo VPN anziché il container.

Verifica la presenza di leak DNS prima di considerare sicura la configurazione

Il DNS (domain name system) è il leak che persiste anche in presenza di un tunnel corretto. Gluetun esegue il proprio resolver all'interno del namespace e inoltra le query tramite DoT (DNS over TLS) verso Cloudflare per impostazione predefinita: DNS_UPSTREAM_RESOLVER_TYPE=dot e DNS_UPSTREAM_RESOLVERS=cloudflare. Non modificare queste impostazioni: le tue ricerche saranno crittografate e transiteranno attraverso il tunnel.

L'impostazione che compromette questa sicurezza è DNS_UPSTREAM_PLAIN_ADDRESSES. Gli utenti la attivano quando un nome non viene risolto e desiderano che sia il router o il resolver del provider a rispondere. La documentazione di Gluetun ne chiarisce le conseguenze: tutto il traffico DNS non passerà attraverso il tunnel VPN e ne uscirà, creando un leak. Il tuo traffico rimane privato, ma l'elenco dei nomi host che visiti no. La versione dello stesso errore relativa a WireGuard è trattata in DNS che smette di risolvere su un tunnel WireGuard.

Per testarlo, imposta HTTPPROXY=on su gluetun e pubblica 8888:8888/tcp, quindi punta un browser verso quel proxy e carica un test per i leak DNS. Il risultato dovrebbe indicare il tuo provider o Cloudflare, mai il tuo router domestico. La documentazione di Gluetun avverte che alcuni test di leak riportano risultati anomali, poiché il resolver all'interno del namespace è un intermediario di caching locale e non il server che fornisce la risposta finale. Considera un paese errato o il resolver del tuo ISP come un segnale di allarme reale.

Aggiungere Tailscale accanto al sidecar VPN e chi ha la precedenza

Tailscale è una rete overlay basata su WireGuard per raggiungere le proprie macchine; molti utenti la eseguono accanto a una VPN di un provider per mantenere un percorso di amministrazione verso lo stack. I due raramente entrano in conflitto, per un motivo che vale la pena comprendere. La documentazione di Tailscale specifica il comportamento predefinito: agisce come rete overlay, instrada il traffico solo tra i dispositivi che eseguono Tailscale e non interferisce con il traffico Internet pubblico.

La risposta dipende quindi da un'impostazione.

  • Tailscale nel proprio container, configurazione predefinita: non vede mai il traffico in uscita dell'applicazione. Gluetun gestisce tutto il traffico. Tailscale raggiunge l'applicazione su gluetun:8080, esattamente come qualsiasi altro container esterno.
  • Tailscale collegato al namespace di gluetun con network_mode: "service:gluetun": richiede il proprio cap_add di net_admin e net_raw, poiché le capability non vengono ereditate con il namespace. Nella modalità di rete userspace predefinita, TS_USERSPACE è attivo, tailscaled non crea alcuna interfaccia e funziona come proxy SOCKS5 o HTTP, quindi non può modificare l'instradamento. Gluetun continua a gestire tutto.
  • Lo stesso, con TS_USERSPACE=false: tailscaled crea un dispositivo tunnel e installa le rotte, ma solo per l'intervallo della tailnet 100.64.0.0/10 più eventuali rotte di subnet pubblicizzate con TS_ROUTES. Il traffico pubblico continua a uscire tramite gluetun.
  • Uno qualsiasi dei casi precedenti con un exit node selezionato, sudo tailscale set --exit-node=<exit-node-ip>: Tailscale rivendica la rotta predefinita e ha la precedenza. Non combinarlo con gluetun. Una sola rotta predefinita, un solo proprietario.

Un effetto collaterale è visibile quando Tailscale viene eseguito all'interno del tunnel. I peer vedono l'indirizzo del provider VPN, quindi è probabile che il sistema ricorra più spesso ai relay. tailscale status mostra relay "..." accanto a un peer invece di direct quando ciò accade. La connessione funziona ed è più lenta. Se la rete overlay è l'unica cosa di cui hai realmente bisogno, la differenza tra WireGuard semplice e Tailscale è il punto di partenza migliore.

Cosa si interrompe e il messaggio che vedrai

Docker rifiuta di creare il container dell'applicazione. Error response from daemon: conflicting options: port publishing and the container type network mode significa che un blocco ports: è ancora presente sul servizio collegato. Spostalo su gluetun.

Compose rifiuta l'intero file. Un servizio non può impostare contemporaneamente network_mode e networks. Sposta le reti su gluetun.

Un altro container non riesce a risolvere l'applicazione. curl: (6) Could not resolve host: qbittorrent è il comportamento corretto, poiché il container collegato non ha aderito ad alcuna rete e non ha registrato alcun nome. Usa gluetun e la porta.

Il secondo container collegato non si avvia. Due processi nello stesso namespace non possono utilizzare la stessa porta; il processo che perde la contesa segnala che l'indirizzo è già in uso. Cambia la porta interna dell'applicazione o avvia un secondo gluetun.

L'applicazione non ha rete dopo aver modificato gluetun. Il riavvio o la ricreazione di gluetun interrompe la connettività per tutto ciò che è ad esso collegato. Riavvia quei container.

Le pagine piccole vengono caricate, quelle grandi si bloccano. Si tratta dell'MTU (maximum transmission unit). Il tunnel aggiunge overhead e un elemento lungo il percorso scarta i pacchetti troppo grandi senza inviare un errore di ritorno. Riduci WIREGUARD_MTU, prova 1400, quindi 1320.

Gluetun non risulta mai in stato healthy. Il controllo all'avvio indica i primi sospettati: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. Verifica se la chiave è scaduta, se l'elenco dei server non è aggiornato o se il firewall del tuo host blocca il traffico UDP in uscita.

FAQ

Perché le porte pubblicate del mio container hanno smesso di funzionare dietro Gluetun?

Perché network_mode: "service:gluetun" inserisce il container all'interno del network namespace di Gluetun, e un namespace possiede un solo indirizzo IP e un unico set di porte in ascolto. L'applicazione continua ad ascoltare, ma la regola di pubblicazione deve risiedere sul container che possiede il namespace. Sposta la lista ports: sul servizio Gluetun. Se la lasci sul servizio collegato, Docker non la creerà nemmeno: Error response from daemon: conflicting options: port publishing and the container type network mode.

Come posso raggiungere un container all'interno del tunnel VPN da un container esterno?

Usa il nome del servizio Gluetun e la porta su cui l'applicazione è in ascolto, ad esempio gluetun:8080. Il container collegato non si trova su alcuna rete Docker propria, quindi il suo nome non viene mai risolto. Non è necessario pubblicare nulla per il traffico tra container. Per la direzione opposta, un container all'interno del namespace raggiunge un container esterno tramite il suo nome di servizio, come postgres:5432, su Gluetun v3.41 e versioni successive. Un client su una subnet diversa, come un portatile sulla tua LAN, viene bloccato dal firewall di Gluetun finché non aggiungi quella subnet a FIREWALL_OUTBOUND_SUBNETS.

Gluetun funziona come kill switch quando la VPN cade?

Sì, e per due ragioni contemporanee. Il container collegato non ha alcuna rotta eccetto quella nel namespace condiviso, quindi un tunnel interrotto lo lascia senza alcun percorso verso l'esterno della macchina. Il firewall di Gluetun inoltre consente il traffico in uscita solo attraverso il tunnel e verso l'endpoint del server VPN. Gluetun riavvia quindi la VPN internamente, registrando WARN [vpn] restarting VPN because it failed to pass the healthcheck, invece di terminare, poiché ogni container collegato perderebbe la rete nel momento in cui Gluetun stesso si riavvia.

Tailscale e Gluetun nello stesso stack: quale gestisce il traffico in uscita?

Gluetun, in ogni configurazione eccetto una. Tailscale instrada il traffico solo tra i dispositivi nella tua tailnet per impostazione predefinita e non interferisce con il traffico pubblico. Nella modalità userspace predefinita dell'immagine del container, non crea alcuna interfaccia, quindi non può influenzare l'instradamento. Con TS_USERSPACE=false installa rotte solo per 100.64.0.0/10 e per le tue subnet pubblicizzate. L'eccezione è un exit node: sudo tailscale set --exit-node=<exit-node-ip> rende Tailscale la rotta predefinita, e in quel caso prevale. Scegli un solo prodotto per gestire la rotta predefinita invece di sovrapporli entrambi.