SSD Nodes Learn 🎉 VPS desde $4.99/mês
Guias Matt ConnorPor Matt Connor

Docker via VPN: portas somem com o Gluetun

Ao usar network_mode: service:gluetun, as portas publicadas e o nome de serviço desaparecem. Veja o erro exato e um compose funcional com Gluetun v3.41.3.

Porque desaparecem as portas quando encaminha contentores Docker através de uma VPN

Para encaminhar contentores Docker através de uma VPN, atribua o túnel a um contentor e ligue os restantes ao respetivo namespace de rede com network_mode: "service:gluetun". É esta ligação que costuma causar dúvidas. O contentor ligado deixa de ter uma rede própria, pelo que perde as portas publicadas e o nome de serviço Docker. Publique as portas no contentor da VPN. Os outros contentores chegam à aplicação através do nome do contentor da VPN.

Se deixar um bloco ports: no contentor ligado, o Docker recusa-se a criá-lo:

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

A ferramenta usada aqui é o Gluetun, um contentor que estabelece ligação a um fornecedor comercial de VPN (rede privada virtual) através de WireGuard ou OpenVPN e inclui a sua própria firewall. A versão v3.41.3 é a atual em agosto de 2026. Os exemplos usam Mullvad com WireGuard, pelo que precisa de uma conta e de uma chave fornecidas pelo seu fornecedor. Se preferir terminar o túnel em hardware que controla, executar o seu próprio servidor WireGuard numa VPS cria a outra extremidade, e wg-easy no Docker disponibiliza essa configuração através de uma interface web.

O que network_mode: "service:gluetun" faz realmente

Normalmente, cada contentor Docker recebe o seu próprio namespace de rede: as suas próprias interfaces, tabela de encaminhamento, regras de firewall e sockets à escuta. O modo service: ignora esse passo e inicia o contentor dentro do namespace do gluetun. Um namespace significa um endereço IP, e isso altera seis aspectos.

  • A aplicação não tem um endereço próprio. Usa o endereço do gluetun.
  • A aplicação não está ligada a nenhuma rede Docker. Por isso, o seu nome de serviço nunca é registado nem resolvido. Os outros contentores têm de usar gluetun.
  • Os contentores dentro do namespace comunicam entre si através de localhost.
  • Dois contentores no mesmo namespace não podem escutar na mesma porta. A documentação do Gluetun é clara: não existe uma solução alternativa.
  • As capabilities pertencem a um contentor, não a um namespace. O Gluetun tem NET_ADMIN e /dev/net/tun porque cria a interface do túnel. O contentor ligado não as herda.
  • O Compose rejeita qualquer ficheiro em que um serviço defina simultaneamente network_mode e networks. Ligue o gluetun às suas redes para que a aplicação use essa ligação.

Reiniciar o gluetun desliga tudo o que está ligado a ele. Esse comportamento está documentado e é a razão pela qual o gluetun reinicia o processo VPN dentro do contentor, em vez de terminar quando a ligação falha. Depois de reiniciar ou recriar o gluetun manualmente, reinicie os contentores ligados a ele.

O ficheiro compose que funciona

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

A tag :v3 é a versão estável mais recente da série v3. A tag :latest aponta para o último commit do branch master, que corresponde à versão de desenvolvimento. Por isso, fixe :v3 numa máquina que não pretende depurar numa terça-feira.

WEBUI_PORT=8080 tem de corresponder à porta publicada, porque o qBittorrent faz bind dentro do namespace do gluetun e a regra de publicação encaminha o tráfego do host para a porta 8080 nesse namespace. Se alterar um número sem alterar o outro, a porta não responde. 127.0.0.1:8080:8080 mantém a interface Web no endereço de loopback do host. Um 8080:8080 sem endereço publica em todas as interfaces e cria a sua própria regra de firewall. É assim que as portas publicadas pelo Docker passam diretamente pelo ufw.

Inicie os serviços e verifique-os nesta ordem:

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

docker compose ps deve mostrar o gluetun como healthy e o qbittorrent como running. Depois, confirme o endereço de saída a partir do namespace. Esta verificação determina o resultado de todas as restantes:

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

O campo ip desse JSON deve conter o endereço do seu fornecedor de VPN. Se contiver o endereço do próprio servidor, a aplicação não está no túnel e nada do que é descrito abaixo funcionará.

Mantenha as chaves fora do ficheiro compose

gluetun.env contém as credenciais e fica fora do git:

WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32

Ambos os valores vêm de um ficheiro de configuração do WireGuard que gera na área da sua conta no fornecedor. Defina o ficheiro com o modo 600. Seja claro sobre o que isto oferece: a chave fica fora do seu repositório, mas docker inspect gluetun continua a imprimir todas as variáveis de ambiente para qualquer pessoa que consiga aceder ao socket do Docker. Ficheiros de ambiente e secrets no Docker Compose aborda opções mais robustas.

Como um container fora do túnel comunica com um container dentro dele

As duas direções funcionam, e cada uma usa um nome diferente. Os dois containers precisam de uma rede Docker partilhada. Essa rede é a rede do gluetun, porque o container associado não tem uma rede própria. Como as redes do Docker Compose são ligadas explica os valores predefinidos.

De fora para dentro, use o nome do gluetun e a porta em que a aplicação escuta. Um container de reverse proxy acede à interface web do qBittorrent em gluetun:8080. Não é necessária nenhuma entrada ports:, porque o tráfego entre containers permanece na rede Docker e nunca passa por uma porta do host.

De dentro para fora, use o nome do serviço do outro container, por exemplo postgres:5432. O Gluetun resolve nomes de outros containers a partir do seu namespace desde a v3.41. Fixe essa versão ou uma mais recente se um nome não for resolvido.

A firewall do Gluetun decide quem pode abrir uma ligação para ele. O tráfego proveniente da própria rede Docker do gluetun é permitido. Um cliente noutra sub-rede, um portátil na sua LAN ou um container numa bridge network separada é descartado até essa sub-rede ser indicada:

FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

O significado documentado é exato: sub-redes separadas por vírgulas às quais o Gluetun e os containers que partilham a sua network stack podem aceder.

As ligações recebidas da Internet são um problema separado. Os peers de um cliente BitTorrent chegam pelo lado da VPN, portanto publicar a porta 6881 no host não tem efeito. É necessária uma porta encaminhada pelo seu provider e essa porta deve ser indicada em FIREWALL_VPN_INPUT_PORTS, que permite portas do lado do servidor VPN. Esta é a parte que a maioria das media stacks criadas com Docker Compose deixa configurada incorretamente.

O interruptor de segurança: o que acontece quando o túnel cai

Este padrão justifica a sua complexidade quando ocorre uma falha. O contentor associado não tem uma segunda rota. O único caminho para sair da máquina é o namespace que partilha. Por isso, quando o túnel está inativo, não existe uma rota alternativa. A firewall do Gluetun aplica a mesma regra no sentido inverso: o tráfego de saída passa pelo túnel ou pelo endpoint do servidor VPN, e todo o restante tráfego é descartado. Não existe um intervalo em que os pacotes possam sair pela interface normal enquanto um cliente restabelece a ligação.

O Gluetun monitoriza a própria ligação. A cada minuto, envia um eco ICMP (um ping) para os endereços em HEALTH_ICMP_TARGET_IPS, que, por predefinição, são 1.1.1.1,8.8.8.8. A cada cinco minutos, estabelece uma ligação TCP e TLS (segurança da camada de transporte) completa para HEALTH_TARGET_ADDRESSES, com o valor predefinido cloudflare.com:443,github.com:443. Quando essas verificações falham, reinicia a VPN dentro do contentor e regista o evento:

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

Leia os logs do contentor associado tendo essa sequência em mente. Linhas como connection refused, operation not permitted e i/o timeout dentro da aplicação são consequências de um túnel inativo, não as causas. A documentação do Gluetun afirma isto explicitamente, porque é comum os administradores identificarem a consequência e investigarem-na durante horas.

HEALTH_RESTART_VPN=on é o valor predefinido e deve permanecer ativado. Desative-o apenas enquanto investiga uma falha específica, porque, quando está desativado, um túnel inativo permanece inativo.

Ordem: impedir que a stack inicie antes de o túnel estar ativo

A imagem inclui um healthcheck do Docker:

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

Esse comando executa uma cópia temporária do gluetun, que consulta o servidor de estado da instância em execução em http://127.0.0.1:9999/. Um túnel funcional responde 200 OK. Um túnel com problemas responde 500 Internal server error com uma mensagem de erro, e o contentor é marcado como não saudável após uma única falha.

condition: service_healthy é o mecanismo que aguarda esse resultado. O depends_on: [gluetun] simples aguarda apenas o arranque do contentor. Esse arranque ocorre vários segundos antes de o handshake ser concluído. Por isso, a aplicação inicia com a rede indisponível e muitas vezes desiste na primeira tentativa de ligação. Healthchecks no Docker Compose explica a sintaxe e os campos de temporização.

Há uma limitação que costuma causar problemas. O Compose avalia essa condição uma vez, quando cria o contentor. Não para nem reinicia a aplicação posteriormente se o gluetun ficar não saudável. O mecanismo interno de recuperação automática do gluetun trata esse caso. Por isso, reinicia o processo VPN em vez do contentor.

Verifique se há um vazamento de DNS antes de confiar na configuração

O DNS (domain name system) é o vazamento que persiste mesmo quando o túnel está configurado corretamente. O Gluetun executa o seu próprio resolvedor dentro do namespace e encaminha as consultas por DoT (DNS over TLS) para o Cloudflare por padrão: DNS_UPSTREAM_RESOLVER_TYPE=dot e DNS_UPSTREAM_RESOLVERS=cloudflare. Não altere nenhuma das duas opções. Assim, as consultas são encriptadas e passam pelo túnel.

A configuração que causa este problema é DNS_UPSTREAM_PLAIN_ADDRESSES. Alguns utilizadores recorrem a ela quando um nome não é resolvido e querem que o router ou o resolvedor do fornecedor responda. A documentação do Gluetun explica claramente a consequência: todo o tráfego DNS deixará de passar pelo túnel VPN e sairá por fora dele, expondo-se. O seu tráfego continua privado. A sua lista de nomes de host não. A versão do WireGuard do mesmo erro é abordada em DNS que deixa de ser resolvido através de um túnel WireGuard.

Para testar, defina HTTPPROXY=on no gluetun e publique 8888:8888/tcp. Em seguida, aponte um browser para esse proxy e carregue um teste de vazamento de DNS. O resultado deve identificar o seu fornecedor ou o Cloudflare, nunca o seu router doméstico. A própria documentação do Gluetun avisa que alguns testes de vazamento apresentam resultados inesperados, porque o resolvedor dentro do namespace é um intermediário local de cache, e não o servidor que responde finalmente. Considere como sinal real um país incorreto ou o resolvedor do seu próprio ISP.

Adicionar o Tailscale junto ao sidecar da VPN e determinar qual prevalece

O Tailscale é uma rede de sobreposição baseada em WireGuard para aceder às suas próprias máquinas. É comum executá-lo junto de uma VPN de um fornecedor para manter um caminho de administração para a stack. Os dois raramente entram em conflito, por uma razão que vale a pena compreender. A documentação do Tailscale define o comportamento predefinido: funciona como uma rede de sobreposição, encaminha tráfego apenas entre dispositivos que executam Tailscale e não interfere no tráfego para a Internet pública.

Por isso, a resposta depende de uma configuração.

  • Tailscale no seu próprio contentor, com a configuração predefinida: nunca vê o tráfego de saída da aplicação. O Gluetun encaminha todo esse tráfego. O Tailscale acede à aplicação através de gluetun:8080, exatamente como qualquer outro contentor externo.
  • Tailscale associado ao namespace do gluetun com network_mode: "service:gluetun": precisa do seu próprio cap_add de net_admin e net_raw, porque as capabilities não são partilhadas com o namespace. No modo predefinido de rede em userspace, TS_USERSPACE está ativo, o tailscaled não cria nenhuma interface e funciona como um proxy SOCKS5 ou HTTP, pelo que não pode alterar o encaminhamento. O Gluetun continua a encaminhar todo o tráfego.
  • O mesmo caso, com TS_USERSPACE=false: o tailscaled cria um dispositivo de túnel e instala rotas, mas apenas para o intervalo da tailnet 100.64.0.0/10 e para quaisquer rotas de sub-rede anunciadas com TS_ROUTES. O tráfego público continua a sair através do gluetun.
  • Qualquer uma das opções anteriores com um exit node selecionado, sudo tailscale set --exit-node=<exit-node-ip>: o Tailscale assume a rota predefinida e prevalece. Não combine essa configuração com o gluetun. Deve existir uma única rota predefinida e um único responsável por ela.

Existe um efeito secundário quando o Tailscale é executado dentro do túnel. Os peers veem o endereço do fornecedor da VPN, pelo que é normal recorrer a relays com mais frequência. tailscale status mostra relay "..." junto a um peer, em vez de direct, quando isso acontece. A ligação funciona, mas fica mais lenta. Se a única funcionalidade necessária era a rede de sobreposição, a diferença entre WireGuard simples e Tailscale é o melhor ponto de partida.

O que falha e a mensagem apresentada

O Docker recusa-se a criar o contentor da aplicação. Error response from daemon: conflicting options: port publishing and the container type network mode significa que ainda existe um bloco ports: no serviço associado. Mova-o para o gluetun.

O Compose recusa o ficheiro inteiro. Um serviço não pode definir network_mode e networks em simultâneo. Coloque as redes no gluetun.

Outro contentor não consegue resolver a aplicação. curl: (6) Could not resolve host: qbittorrent é o comportamento correto, porque o contentor associado não entrou em nenhuma rede nem registou nenhum nome. Use gluetun e a porta.

O segundo contentor associado não inicia. Dois processos no mesmo namespace não podem associar a mesma porta. O processo que perde indica que o endereço já está a ser utilizado. Altere a porta interna da aplicação ou execute um segundo gluetun.

A aplicação fica sem rede depois de alterar o gluetun. Reiniciar ou recriar o gluetun interrompe a conectividade de tudo o que está associado a ele. Reinicie esses contentores.

As páginas pequenas carregam, mas as grandes ficam bloqueadas. Isso é MTU (unidade máxima de transmissão). O túnel adiciona overhead, e algum ponto do caminho descarta os pacotes demasiado grandes sem devolver um erro. Reduza WIREGUARD_MTU, experimente 1400 e depois 1320.

O Gluetun nunca fica saudável. A verificação de arranque indica as primeiras causas a investigar: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. Verifique se a chave expirou. Depois, confirme se a lista de servidores está desatualizada. Por fim, confirme se a firewall do host bloqueia tráfego UDP de saída.

FAQ

Por que as portas publicadas do meu container deixaram de funcionar atrás do Gluetun?

Porque network_mode: "service:gluetun" coloca o container no namespace de rede do gluetun, e um namespace tem um único endereço IP e um único conjunto de portas em escuta. A aplicação continua a escutar, mas a regra de publicação tem de ficar no container que possui o namespace. Mova a lista ports: para o serviço gluetun. Se a deixou no serviço associado, o Docker nem sequer a cria: Error response from daemon: conflicting options: port publishing and the container type network mode.

Como acedo a um container dentro do túnel VPN a partir de um container fora dele?

Use o nome do serviço do gluetun e a porta em que a aplicação escuta, por exemplo gluetun:8080. O container associado não está ligado à sua própria rede Docker, por isso o seu próprio nome nunca é resolvido. Não é necessário publicar portas para o tráfego entre containers. No sentido inverso, um container dentro do namespace acede a um container externo pelo nome do serviço, como postgres:5432, no Gluetun v3.41 e posteriores. Um cliente noutra sub-rede, como um portátil na sua LAN, é bloqueado pela firewall do gluetun até adicionar essa sub-rede a FIREWALL_OUTBOUND_SUBNETS.

O Gluetun funciona como um kill switch quando a VPN cai?

Sim, por duas razões. O container associado não tem outra rota além da existente no namespace partilhado, por isso um túnel interrompido deixa-o sem caminho para sair da máquina. A firewall do Gluetun também permite tráfego de saída apenas através do túnel e para o endpoint do servidor VPN. O Gluetun reinicia internamente a VPN e regista WARN [vpn] restarting VPN because it failed to pass the healthcheck, em vez de terminar, porque todos os containers associados perdem a rede quando o próprio gluetun reinicia.

Tailscale e Gluetun na mesma stack: qual deles encaminha o tráfego de saída?

O Gluetun, em todas as configurações exceto uma. Por predefinição, o Tailscale encaminha apenas o tráfego entre dispositivos da sua tailnet e deixa o tráfego público inalterado. No modo userspace predefinido da imagem do container, não cria qualquer interface, por isso não pode afetar o encaminhamento. Com TS_USERSPACE=false, instala rotas apenas para 100.64.0.0/10 e para as suas sub-redes anunciadas. A exceção é um exit node: sudo tailscale set --exit-node=<exit-node-ip> torna o Tailscale a rota predefinida, que passa a ser utilizada. Escolha um único produto para gerir a rota predefinida, em vez de usar ambos em conjunto.