SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-24

Como encaminhar contentores Docker por uma VPN

Com Gluetun, as portas publicadas desaparecem ao usar network_mode: service. Veja o motivo, o erro do Docker e o compose correto para expor portas.

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

Para encaminhar contentores Docker através de uma VPN, atribui o túnel a um contentor e liga os restantes ao respetivo namespace de rede com network_mode: "service:gluetun". Essa ligação é a parte que costuma causar dúvidas. O contentor ligado deixa de ter uma rede própria, por isso as portas publicadas e o nome de serviço Docker desaparecem com ela. Publique as portas no contentor VPN. Os outros contentores acedem à aplicação através do nome do contentor 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 neste exemplo é 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, por isso 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 na prática

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 em 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 é explícita: não existe 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, e a aplicação acompanha-o.

Reiniciar o gluetun desliga tudo o que está ligado a ele. Este 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 funcional

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 a meio da semana.

WEBUI_PORT=8080 tem de corresponder à porta publicada, porque o qBittorrent escuta 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 acessível apenas no endereço de loopback do host. Um 8080:8080 isolado publica em todas as interfaces e cria a sua própria regra de firewall. É assim que as portas publicadas pelo Docker ultrapassam diretamente o ufw.

Inicie os serviços e depois 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. Em seguida, confirme o endereço de saída a partir do namespace. Esta verificação determina o resultado de todas as outras:

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

O campo ip nesse 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 irá 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 do provedor. 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 mostrar todas as variáveis de ambiente a qualquer pessoa que consiga aceder ao socket do Docker. Ficheiros de ambiente e segredos no Docker Compose aborda opções mais robustas.

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

As duas direções funcionam, e cada uma usa um nome diferente. Os dois contentores precisam de uma rede Docker partilhada. Essa rede é a rede do gluetun, porque o contentor 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 onde a aplicação escuta. Um contentor de reverse proxy acede à interface Web do qBittorrent em gluetun:8080. Não é necessária nenhuma entrada ports:, porque o tráfego entre contentores permanece na rede Docker e nunca usa uma porta do host.

De dentro para fora, use o nome do serviço do outro contentor, por exemplo postgres:5432. Desde a versão v3.41, o Gluetun resolve os nomes dos outros contentores a partir do seu namespace. 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 contentor numa rede bridge separada é bloqueado até indicar essa sub-rede:

FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

O significado documentado é exato: sub-redes separadas por vírgulas às quais o Gluetun e os contentores que partilham a sua pilha de rede têm permissão para aceder.

As ligações recebidas da Internet são um problema separado. Os peers de um cliente de torrents chegam pelo lado da VPN, por isso publicar a porta 6881 no host não tem qualquer efeito para eles. Precisa de uma porta encaminhada pelo seu fornecedor e de a indicar em FIREWALL_VPN_INPUT_PORTS, que permite portas do lado do servidor VPN. Esta é a parte que a maioria das pilhas de multimédia criadas com Docker Compose deixa por configurar.

O kill switch: 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 do outro lado: 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 uma janela em que os pacotes possam sair pela interface normal enquanto o cliente restabelece a ligação.

O Gluetun monitoriza a própria ligação. A cada minuto, envia um echo 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 (transport layer security) completa para HEALTH_TARGET_ADDRESSES, com cloudflare.com:443,github.com:443 como valor predefinido. Quando estas 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 ordem em consideração. 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 causas. A documentação do Gluetun afirma isto explicitamente, porque muitas pessoas reportam a consequência e procuram a causa durante horas.

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

Ordenação: impedir que a stack arranque 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 segunda cópia temporária do gluetun, que consulta o servidor de healthcheck da instância em execução em http://127.0.0.1:9999/. Um túnel funcional responde com 200 OK. Um túnel com falhas responde com 500 Internal server error e uma mensagem de erro, e o container é marcado como não saudável após uma única falha.

condition: service_healthy é o que aguarda essa condição. O depends_on: [gluetun] simples aguarda apenas que o container arranque. Isso acontece vários segundos antes de o handshake terminar. Assim, a aplicação arranca numa 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 única vez, quando cria o container. Não para nem reinicia a aplicação posteriormente se o gluetun ficar não saudável. A recuperação automática interna do gluetun trata esse caso. Por isso, reinicia o processo VPN em vez do container.

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

O DNS (sistema de nomes de domínio) é 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 nenhum dos dois. As consultas ficam encriptadas e passam pelo túnel.

A configuração que causa o problema é DNS_UPSTREAM_PLAIN_ADDRESSES. As pessoas recorrem a ela quando um nome não é resolvido e querem que o router ou o resolvedor do provedor responda em seu lugar. A documentação do Gluetun explica claramente o custo: todo o tráfego DNS não passará pelo túnel VPN e sairá por fora dele. O seu tráfego continua privado. A sua lista de nomes de host não. A mesma falha na versão com WireGuard é 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 navegador para esse proxy e carregue um teste de vazamento de DNS. O resultado deve identificar o seu provedor ou o Cloudflare, nunca o seu router doméstico. A própria documentação do Gluetun alerta que alguns testes de vazamento apresentam resultados inesperados, porque o resolvedor dentro do namespace é um intermediário local com cache, e não o servidor que responde finalmente. Considere um país incorreto ou o resolvedor do seu próprio ISP como o sinal real.

Adicionar o Tailscale ao lado do sidecar da VPN e determinar qual prevalece

O Tailscale é uma rede sobreposta baseada em WireGuard para aceder às suas próprias máquinas. Muitas pessoas executam-no ao lado de uma VPN de um provedor para manter um caminho de administração para a stack. Normalmente, os dois não entram em conflito, por um motivo que vale a pena compreender. A documentação do Tailscale define o comportamento padrão: ele funciona como uma rede sobreposta, encaminha tráfego apenas entre dispositivos que executam Tailscale e não interfere no tráfego público de internet.

A resposta depende de uma configuração.

  • Tailscale no seu próprio container, com a configuração padrão: nunca vê o tráfego de saída da aplicação. O Gluetun transporta todo esse tráfego. O Tailscale acede à aplicação em gluetun:8080, exatamente como qualquer outro container 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 transferidas com o namespace. No modo padrão de rede em userspace, TS_USERSPACE está ativado. O tailscaled não cria nenhuma interface e funciona como proxy SOCKS5 ou HTTP, portanto não pode alterar o encaminhamento. O Gluetun continua a transportar 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 do 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 pelo gluetun.
  • Qualquer um dos casos anteriores com um exit node selecionado, sudo tailscale set --exit-node=<exit-node-ip>: o Tailscale assume a rota padrão e prevalece. Não combine essa configuração com o gluetun. Uma rota padrão, um único responsável.

Se essas rotas anunciadas forem o objetivo e quiser que toda a rede privada atrás da máquina fique acessível, em vez de apenas a própria máquina, executar um roteador de sub-rede Tailscale numa VPS explica a aprovação da rota, o encaminhamento de IP e a flag do lado do cliente que TS_ROUTES, por si só, não configura.

Se o Tailscale estiver presente para fornecer um URL de administração, em vez de uma rota, tailscale serve e tailscale funnel colocam HTTPS na frente de gluetun:8080 para o seu tailnet, sendo que apenas o funnel o expõe à internet pública.

Um efeito secundário é visível quando o Tailscale é executado dentro do túnel. Os peers veem o endereço do provedor VPN, portanto é esperado que o Tailscale recorra a relays com mais frequência. tailscale status mostra relay "..." ao lado de um peer, em vez de direct, quando isso acontece. A ligação funciona, mas fica mais lenta. Se a única coisa de que realmente precisava era da rede sobreposta, a diferença entre WireGuard simples e Tailscale é um ponto de partida melhor.

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 um bloco ports: ainda está associado ao serviço anexado. Mova-o para o gluetun.

O Compose recusa o ficheiro inteiro. Um serviço não pode definir network_mode e networks ao mesmo tempo. 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 anexado não aderiu a nenhuma rede nem registou um nome. Use gluetun e a porta.

O segundo contentor anexado não arranca. Dois processos no mesmo namespace não podem associar a mesma porta, e 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 algo no 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 os primeiros suspeitos: 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 se a lista de servidores está desatualizada e, por fim, se a firewall do host bloqueia UDP de saída.

FAQ

Porque é que as portas publicadas do meu contentor deixaram de funcionar atrás do Gluetun?

Porque network_mode: "service:gluetun" coloca o contentor no namespace de rede do gluetun, e um namespace tem um endereço IP e um conjunto de portas de escuta. A aplicação continua a escutar, mas a regra de publicação tem de ficar no contentor 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 chego a um contentor dentro do túnel VPN a partir de um contentor fora dele?

Use o nome do serviço gluetun e a porta em que a aplicação escuta, por exemplo gluetun:8080. O contentor associado não está ligado a nenhuma rede Docker própria, por isso o seu próprio nome nunca é resolvido. Não é necessário publicar portas para tráfego entre contentores. No sentido inverso, um contentor dentro do namespace chega a um contentor externo através do nome do serviço, como postgres:5432, no Gluetun v3.41 e posteriores. Um cliente numa sub-rede diferente, 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 ao mesmo tempo. O contentor 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 então a VPN internamente e regista WARN [vpn] restarting VPN because it failed to pass the healthcheck, em vez de terminar, porque todos os contentores associados perdem a rede quando o próprio gluetun reinicia.

Tailscale e Gluetun na mesma stack: qual deles transporta 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 contentor, não cria nenhuma interface, por isso não pode afetar o encaminhamento. Com TS_USERSPACE=false, instala apenas rotas 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> faz do Tailscale a rota predefinida e, nesse caso, é ele que prevalece. Escolha um produto para controlar a rota predefinida, em vez de usar os dois em conjunto.