O contêiner não alcança a API disponível apenas por VPN? Faça um proxy no host
O túnel vive no host, então o contêiner não tem rota para o endereço privado. Publique um proxy reverso estreito e chame-o como host.docker.internal.
Seu serviço do Compose expira contra uma API que o navegador na mesma máquina abre sem qualquer reclamação. Essa diferença é a pista: a requisição que funciona sai de um host com túnel VPN, e a que trava sai de um contêiner com sua própria pilha de rede. Nada dentro do contêiner conhece as rotas do túnel, então o destino privado continua inalcançável, não importa como o nome seja escrito. A correção é um salto deliberado pelo host, não uma tabela de rotas mais esperta.
Comece por por que os dois caminhos diferem e depois decida onde o túnel deve viver.
Por que o host funciona e o contêiner não
Subir o túnel dá ao host uma interface, um conjunto de rotas e, normalmente, uma entrada de resolvedor fornecida pela VPN. Um contêiner não herda nada disso: ele começa com uma interface na ponte do motor e uma rota padrão que aponta para essa ponte. As rotas instaladas no host são invisíveis para tudo o que roda dentro desse espaço de nomes.
Por isso os sintomas parecem inconsistentes e não simplesmente quebrados. O host resolve o nome privado e conecta, porque ali existem tanto o nome quanto a rota. O contêiner envia seu pacote para um endereço privado que nada no caminho da ponte consegue alcançar, e a requisição expira. Adicionar o nome ao arquivo hosts do contêiner não muda nada, porque o próprio endereço é inalcançável de onde o contêiner está.
Alcance o host de dentro do contêiner
Um processo no contêiner que disca localhost está discando o contêiner. O Docker Desktop publica host.docker.internal como um nome que aponta de volta para a máquina que executa o motor; os motores Linux o deixam indefinido a menos que o serviço peça, e é isso que o mapeamento abaixo faz. Com isso, a aplicação mira um nome e uma porta em vez do endereço privado.
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
INTERNAL_API_HOST: "host.docker.internal:8666"O túnel fica no lado do host do salto, e a porta nessa variável é aquela em que o proxy vai escutar. Um contêiner que perde o túnel agora falha em um único salto conhecido em vez de tentar uma rota que nunca teve.
Escolha onde o túnel vive
Três formas são possíveis para um serviço do Compose que precisa de uma API privada, e elas diferem em qual processo é dono do túnel. Um cliente VPN pode rodar dentro da imagem do contêiner, o que exige expor um dispositivo de túnel e conceder a capacidade de administração de rede que vem com ele. A aplicação pode compartilhar o espaço de nomes de rede de um contêiner de túnel separado, de modo que os dois processos veem uma pilha e uma tabela de rotas. Ou o túnel pode ficar no host, com um encaminhador estreito do host para o contêiner.
As duas primeiras puxam arquivos de perfil, comportamento de reconexão e política de resolvedor para dentro do projeto, e fazem cada serviço do arquivo depender da saúde do túnel. É acoplamento demais para uma única chamada de API. Para uma pilha de desenvolvimento que lê um único endpoint interno, o encaminhador do lado do host é a menor mudança que ainda mantém as credenciais onde a equipe de plataforma as administra.
Publique um único caminho estreito no host
Comece pelo que o encaminhador herda: execute-o com a conta que é dona do túnel e seus soquetes de saída assumem as rotas do host e o resolvedor do host. Um bloco location do nginx cobre um caminho.
server {
listen 8666;
location /search/ {
proxy_pass http://10.20.30.40:8090/;
proxy_set_header Host internal-api.example;
proxy_read_timeout 60s;
}
}Trate o endereço e a porta como substitutos e coloque o par em que a API realmente escuta. Cada upstream adicional merece seu próprio bloco location, e cada bloco deve admitir apenas os caminhos que este serviço chama, para que o proxy não se torne uma porta geral para a rede privada. Amarre o ouvinte ao endereço da ponte para clientes que chegam via host-gateway, ao loopback só quando o cliente compartilha o espaço de nomes de rede do host, e nunca a 0.0.0.0. Escolha também uma porta ainda livre: um número já ocupado deixa o proxy sem escutar enquanto o contêiner continua expirando, o que é indistinguível da falha de rede com que você começou.
Portas, nomes e a decisão de TLS
A resolução de nomes se divide do mesmo jeito que o roteamento. As consultas públicas do contêiner passam pelo resolvedor do motor, então um nome privado que resolve no host não vai resolver dentro do contêiner. O proxy é o componente que resolve o nome privado, porque herda o resolvedor do host.
TLS é a decisão que é adiada e depois explica a próxima falha. Se o proxy encaminha os bytes intactos, o contêiner vê o certificado da própria API e precisa da AC interna em seu armazenamento de confiança. Se o proxy termina o TLS, essa confiança passa para o host e a aplicação começa a montar links a partir do cabeçalho host que recebe. Escolha um arranjo e aplique-o a cada recurso, porque um redirecionamento emitido pelo outro aponta para um nome de host que o contêiner não consegue resolver.
Verifique de dentro do serviço
Envie a requisição do contêiner, não do host: um curl simples contra o caminho do proxy e depois a verificação de saúde da própria aplicação. Quando o proxy responde mas a aplicação ainda falha, compare três coisas: a URL base que ela monta, qualquer variável de proxy em seu ambiente e a AC em que ela confia. Todas estão no ambiente e nos registros do contêiner, e corrigir qualquer uma delas deixa a VPN em paz.
Escreva o arranjo escolhido no README do projeto enquanto está fresco. Senão, a próxima pessoa que esbarrar nesse tempo esgotado passará a mesma tarde redescobrindo que o túnel vive no host.