SSD Nodes Learn 🎉 VPS desde $4.99/mês
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-07

Gluetun: aceder ao host e a outros contentores

Contentores no namespace do Gluetun não têm interfaces próprias: publique portas no Gluetun e permita apenas as sub-redes necessárias fora do túnel.

O que acontece quando um contentor entra na rede do gluetun

Um contentor que define network_mode: service:gluetun não tem interfaces de rede próprias. Entra no namespace de rede do gluetun, pelo que a publicação de portas e as regras de firewall deixam de ser propriedades desse contentor e passam a ser propriedades do serviço gluetun. Todas as respostas abaixo resultam desse facto.

Um namespace de rede é uma cópia privada da pilha de rede do kernel: tem as suas próprias interfaces, tabela de encaminhamento, regras de firewall e sockets em escuta. Por predefinição, o Docker atribui um a cada contentor. Quando escreve network_mode: service:gluetun, o Docker ignora esse passo e coloca o novo contentor dentro do namespace que o gluetun já possui. O contentor mantém o seu próprio sistema de ficheiros e o seu próprio ficheiro /etc/hosts, e esse segundo ficheiro será importante mais adiante.

Pode ver isso diretamente.

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

Isto apresenta container:, seguido do ID do contentor gluetun, quando um contentor normal apresentaria bridge. Este guia continua a partir de encaminhar o tráfego do Docker por uma VPN com o gluetun: o túnel funciona, mas agora nada consegue comunicar com o contentor.

Publique a porta no gluetun, não na aplicação

Deixe um bloco ports: no serviço que define network_mode e o Docker recusa-se a criar o contentor:

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

O motivo é direto. Publicar uma porta significa adicionar uma regra NAT (tradução de endereços de rede) que encaminha uma porta do host para o namespace de rede do próprio contentor, mas este contentor não tem um namespace de rede próprio. Mova o mapeamento para o serviço gluetun. O número da porta não muda, porque a aplicação continua a escutar nessa porta dentro do namespace partilhado.

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

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

Um bloco expose: no serviço dependente também não tem utilidade, e um bloco networks: nesse serviço impede o arranque: o Compose informa que o serviço declara network_mode e networks, que são mutuamente exclusivos, e recusa-se a carregar o ficheiro.

Uma consequência surge mais tarde. Todos os contentores no namespace partilham um único espaço de portas. Por isso, duas aplicações que usem 8080 por predefinição entram em conflito, e a segunda a iniciar falha com um erro de endereço já em utilização. Altere uma delas na própria configuração, por exemplo a variável WEBUI_PORT na imagem LinuxServer qBittorrent, e depois publique o novo número no gluetun.

Como os containers atrás do gluetun comunicam entre si?

Dentro do namespace, eles já partilham uma interface de loopback. Um container atrás do gluetun acede ao seu container irmão em 127.0.0.1:<port> sem envolver uma rede Docker.

A partir do exterior do namespace, o container não tem nome. O DNS integrado do Docker resolve um nome de serviço para o endereço desse serviço numa rede definida pelo utilizador, mas este container não tem um endereço em nenhuma rede. Por isso, um container normal, como o Sonarr, não acede ao cliente de torrents em http://qbittorrent:8080. Acede-lhe em http://gluetun:8080, porque o socket está à escuta no namespace do gluetun, no endereço do gluetun. Isto surpreende quem conhece como funcionam as redes e os nomes de serviço do Docker Compose e espera que a nomenclatura habitual se aplique. Também funciona sem publicar nada no host, porque ambos os containers estão na mesma rede do Compose.

Verifique o DNS antes de depurar qualquer outra coisa. O gluetun executa o seu próprio resolver e reescreve /etc/resolv.conf no seu próprio container, mas /etc/resolv.conf é um ficheiro específico de cada container, portanto o ficheiro que o gluetun escreveu não é o ficheiro que a sua aplicação lê.

docker exec qbittorrent cat /etc/resolv.conf

Como acedo a um serviço em execução no host Docker?

Use host.docker.internal. São necessárias duas definições em dois locais diferentes, porque há duas falhas diferentes.

O nome vem primeiro. /etc/hosts é definido por contentor, por isso a entrada extra_hosts pertence ao contentor da aplicação, não ao gluetun.

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

host-gateway é um valor especial que o Docker substitui pelo endereço interno do próprio host. Numa instalação Linux simples do Docker, esse é o endereço da bridge docker0, normalmente 172.17.0.1. Confirme o seu com ip -4 addr show docker0 na VPS. O Docker Desktop resolve este nome automaticamente. Por isso, os guias escritos para um portátil omitem a linha extra_hosts, e o mesmo ficheiro falha depois num servidor.

A rota vem depois. Adicionar o nome apenas informa o contentor sobre o endereço a utilizar. O pacote continua a sair pela rota predefinida do gluetun, que é o túnel, e a firewall do gluetun descarta-o. O sintoma é uma ligação que fica pendente e depois excede o tempo limite, não uma ligação recusada. Uma recusa significa que o pacote chegou e algo respondeu negativamente. Um tempo limite significa que o pacote nunca chegou.

  gluetun:
    environment:
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32

Depois, confirme se o serviço no host está efetivamente a escutar nesse endereço. Um servidor PostgreSQL ligado apenas a 127.0.0.1 não pode ser acedido a partir de nenhum contentor, com ou sem túnel, porque 127.0.0.1 dentro do namespace é o loopback desse próprio namespace. Ligue-o a 172.17.0.1: aceitará ligações dos contentores sem ficar exposto na interface pública. Confirme com ss -lntp | grep 5432 no host.

O que FIREWALL_OUTBOUND_SUBNETS realmente altera

A documentação do gluetun descreve esta variável como as sub-redes, separadas por vírgulas, às quais o gluetun e os contentores que partilham a sua pilha de rede podem aceder. Também indica que a configuração envolve alterações à firewall e ao encaminhamento. As duas partes são importantes. O gluetun adiciona uma rota para cada sub-rede listada através do gateway da bridge Docker. Assim, os pacotes destinados a esses endereços saem por eth0, em vez de passarem pelo túnel. Também abre a firewall para essas sub-redes, porque, caso contrário, o gluetun descarta o tráfego de saída que não é destinado ao servidor VPN.

Escreva o valor sem espaços depois das vírgulas.

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

Há duas propriedades fáceis de ignorar. Esta é uma configuração ao nível do namespace. Por isso, aplica-se a todos os contentores atrás do gluetun, e não apenas ao contentor pretendido. Além disso, aplica-se apenas ao tráfego de saída: controla as ligações iniciadas por um contentor. As ligações que chegam a uma porta publicada seguem um caminho diferente e não precisam de ser incluídas aqui.

Aceder à interface web a partir de um peer Tailscale

O Tailscale atribui a cada máquina um endereço em 100.64.0.0/10, o intervalo reservado para NAT de operadora. As duas direções exigem configurações diferentes.

A entrada é simples. Publicar 8080:8080 no gluetun associa essa porta a todos os endereços do host. A interface tailscale0 do host é um deles. Por isso, um peer abre http://<machine-name>:8080 e chega ao contentor. O Gluetun não participa nesse percurso, porque a regra de NAT do Docker fica no host, fora do namespace.

Para tornar a interface acessível apenas através da tailnet, associe a porta publicada ao endereço Tailscale do host, em vez de a todos os endereços.

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

Obtenha esse endereço com tailscale ip -4 no host. Aqui, associar a porta é um controlo mais forte do que uma regra de firewall, porque a porta nunca é aberta na interface pública. Isto também evita o problema descrito em Publicar portas do Docker diretamente além do ufw.

A saída é onde FIREWALL_OUTBOUND_SUBNETS volta a ser relevante. Se um contentor tiver de contactar um peer, adicione o endereço desse peer. Prefira um /32 por peer em vez de toda a /10. Os nomes do MagicDNS não serão resolvidos dentro do contentor, porque o contentor não usa o resolvedor do host. Por isso, use o endereço numérico 100.x ou fixe-o com uma linha extra_hosts. O mesmo se aplica quando executa o seu próprio servidor de controlo Tailscale com Headscale.

Um ficheiro compose completo para o padrão comum

Um cliente de downloads atrás da VPN, duas interfaces Web acessíveis apenas na tailnet e um contentor que lê uma base de dados PostgreSQL em execução no 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

Leia o ficheiro para compreender o padrão, não os nomes dos produtos. As duas interfaces Web são publicadas no gluetun e associadas ao endereço da tailnet do host. Assim, respondem na Tailscale e em nenhum outro local. Apenas o Prowlarr contém a linha extra_hosts, porque é o contentor que resolve nomes host.docker.internal. FIREWALL_OUTBOUND_SUBNETS define dois endereços individuais: o endereço da bridge Docker do host, para que o Prowlarr possa abrir uma ligação à base de dados, e um peer da tailnet.

O servidor PostgreSQL está deliberadamente ausente do ficheiro. É executado na VPS como um serviço normal do sistema e fica à escuta em 172.17.0.1:5432. Esta é a mesma separação usada em uma stack arr no Docker Compose, com a base de dados fora do Docker.

Mantenha a chave privada WireGuard fora do ficheiro compose. ${WIREGUARD_PRIVATE_KEY} lê o conteúdo de um ficheiro .env colocado junto dele. Este é o padrão abordado em ficheiros de ambiente e secrets para o Docker Compose. A cláusula condition: service_healthy usa o healthcheck que a imagem gluetun já fornece. Assim, nada é iniciado até o túnel indicar que está ativo. Os healthchecks do Compose explicam a estrutura geral.

Publicar em todos os endereços em vez de apenas na tailnet

Remova o prefixo do endereço e as associações de portas em 0.0.0.0. Isto inclui o IP público da VPS. Faça-o apenas atrás de uma firewall que controle e leia primeiro a nota sobre ufw acima.

    ports:
      - "8080:8080/tcp"

Confirme se o túnel continua a transportar tráfego

Execute o mesmo pedido duas vezes: uma a partir do namespace e outra a partir do host. Compare os resultados.

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

O primeiro deve mostrar o endereço de saída do seu provedor de VPN. O segundo deve mostrar o endereço do VPS. Se forem iguais, o tráfego do contentor não está a passar pelo túnel. Até corrigir isso, todas as correções deste guia são irrelevantes.

A tabela de encaminhamento mostra o que passa pelo túnel e o que não passa.

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

A rota predefinida deve apontar para a interface do túnel, tun0. Abaixo dela, deverá ver uma rota para cada entrada em FIREWALL_OUTBOUND_SUBNETS, apontando para o gateway da bridge Docker. Qualquer outra rota que saia por eth0 corresponde a tráfego que ignora a VPN.

O servidor de controlo do Gluetun fornece o mesmo IP público na porta 8000, em /v1/publicip/ip. As versões recentes exigem que configure a autenticação para as rotas do servidor de controlo. Configure-a antes de depender desse servidor.

A fuga causada por uma sub-rede demasiado ampla

FIREWALL_OUTBOUND_SUBNETS é uma abertura criada intencionalmente na firewall, por isso o tamanho da abertura corresponde ao tamanho do risco. Há quatro formas de a tornar demasiado ampla:

  • 0.0.0.0/0 envia tudo para fora do túnel. As duas verificações de IP acima detetam isto na primeira execução, porque devolvem o mesmo endereço.
  • Um intervalo mais amplo do que o necessário. Abrir 10.0.0.0/8 para chegar a uma máquina em 10.0.1.7 também abre todos os endereços que um peer de torrent possa anunciar nesse intervalo. Escreva 10.0.1.7/32.
  • Um intervalo que se sobrepõe aos próprios endereços do túnel. A documentação do gluetun avisa que isto faz o gluetun enviar o tráfego VPN através da bridge, o que interrompe o encaminhamento de portas. Verifique o valor de WIREGUARD_ADDRESSES antes de abrir qualquer intervalo privado.
  • 100.64.0.0/10 para Tailscale. Isto abre aproximadamente quatro milhões de endereços para que um peer possa ser alcançado. Liste os peers necessários como entradas /32.

Lembre-se de que esta definição abrange todo o namespace. Abrir uma sub-rede para que um indexer possa chegar a um serviço do host também abre a mesma sub-rede para o cliente de torrent que partilha esse namespace. Execute novamente a verificação do IP público depois de cada alteração a esta variável, porque é o único teste que mostra se a alteração teve o efeito pretendido.

O que falha quando reinicia o gluetun

O gluetun controla o namespace, portanto o ciclo de vida do gluetun é o ciclo de vida do namespace. Iniciar um contentor dependente enquanto o gluetun está parado falha imediatamente:

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

Reiniciar o gluetun no local é uma falha mais silenciosa. Os contentores dependentes continuam em execução enquanto o namespace ao qual estão ligados é recriado por baixo deles. Por isso, docker ps indica que está tudo saudável, mas nenhum serviço responde. Depois de qualquer alteração ao serviço gluetun, recrie o grupo inteiro em vez de reiniciar apenas uma parte.

docker compose up -d --force-recreate

O mesmo se aplica às atualizações da imagem. Transferir uma nova imagem do gluetun e recriar apenas esse serviço deixa os outros contentores ligados a um namespace que já não existe.

FAQ

Por que o Docker exibe "port publishing and the container type network mode"?

Isso acontece porque ainda existe um bloco ports: num serviço que também define network_mode: service:gluetun. A publicação de uma porta adiciona uma regra NAT que encaminha uma porta do host para o namespace de rede próprio de um container, mas um container neste modo não tem um namespace próprio. Remova o bloco ports: desse serviço e adicione o mesmo mapeamento ao serviço gluetun. O número da porta permanece igual, porque a aplicação continua a escutar nessa porta dentro do namespace partilhado.

Como é que outros containers acedem a um serviço que está atrás do gluetun?

Os containers no mesmo namespace acedem uns aos outros através de 127.0.0.1. Os containers que estão fora dele usam o nome do serviço gluetun; por isso, http://gluetun:8080 funciona, mas http://qbittorrent:8080 não. O container da aplicação não tem um endereço em nenhuma rede Docker, pelo que o servidor DNS integrado não tem nada para resolver para o nome desse container. Não é necessária a publicação de uma porta, desde que ambos os containers partilhem uma rede Compose.

O que devo colocar em FIREWALL_OUTBOUND_SUBNETS?

Apenas os endereços aos quais um container atrás do gluetun precisa de iniciar uma ligação, especificados da forma mais restrita possível. Uma única máquina é um /32. As duas entradas mais comuns são o host Docker em 172.17.0.1/32 e um /32 para cada peer Tailscale ao qual se liga. Nunca adicione 0.0.0.0/0 e nunca adicione um intervalo que se sobreponha aos próprios endereços do túnel VPN. As ligações de entrada para uma porta publicada não precisam de nenhuma entrada aqui.

Por que o container não consegue resolver os meus nomes MagicDNS do Tailscale?

O MagicDNS funciona ao apontar o resolver do host para o servidor DNS do Tailscale, mas o container não usa o resolver do host. Usa o que estiver definido no seu próprio /etc/resolv.conf; atrás do gluetun, isso corresponde à configuração DNS do gluetun. Confirme com docker exec <container> cat /etc/resolv.conf. Use o endereço numérico 100.x do peer ou associe o nome a um endereço através de uma entrada extra_hosts nesse container.

Como confirmo que o tráfego continua a passar pela VPN?

Execute um pedido dentro do namespace e o mesmo pedido a partir do host. Depois, compare as respostas. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org deve devolver o endereço de saída do seu fornecedor de VPN, enquanto curl -s https://api.ipify.org no VPS devolve o endereço do VPS. Duas respostas iguais significam que o túnel não está a transportar o tráfego do container. Repita esta verificação depois de cada alteração a FIREWALL_OUTBOUND_SUBNETS.