Conectar um proxy residencial a um container parece trivial até não parecer. Você define HTTP_PROXY, executa a imagem, e as requisições ainda saem pelo IP do host. Ou o proxy funciona, mas seus health checks começam a falhar porque chamadas de serviço internas agora estão sendo tunneladas por uma saída residencial em outro país. Ou, pior de tudo, suas credenciais acabam gravadas em uma camada de imagem.
Nenhum desses casos é exótico. São as quatro coisas que costumam pegar equipes que colocam proxies em containers: onde a configuração se aplica, o que deve contorná-la, como os segredos entram, e como a identidade é mapeada para os containers. Este guia cobre cada um desses pontos, com configuração funcional, para DevOps e engenheiros de plataforma.
A única distinção que explica a maior parte da confusão
O Docker tem dois conceitos de proxy completamente separados, e misturá-los é a raiz da maioria dos chamados de “por que meu proxy não está funcionando”:
- Proxy de build-time / daemon — configurado em
~/.docker/config.jsonou via--build-arg. Isso rege o daemon do Docker (fazer pull de imagens) e o processo de build (RUN apt-get install). Isso não tem nada a ver com o tráfego de runtime da sua aplicação. - Proxy de runtime — variáveis de ambiente dentro do container em execução. É isso que o código da sua aplicação enxerga.
Se você configurou um proxy em ~/.docker/config.json e esperava que seu scraper Python o usasse, esse é o bug. Essas configurações nunca chegam ao ambiente de runtime do container.
E mais uma camada: mesmo em runtime, HTTP_PROXY é uma convenção, não uma imposição. O kernel não roteia o tráfego por ele. Cada biblioteca escolhe se vai respeitá-lo ou não:
| Cliente | Respeita HTTP_PROXY/HTTPS_PROXY? |
|---|---|
curl, wget | Sim |
Python requests, httpx | Sim (por padrão) |
Node fetch / undici | Não, precisa de um dispatcher/agent explícito |
Go net/http | Sim, via http.ProxyFromEnvironment (o transporte padrão) |
| Chromium / Playwright / Puppeteer | Não, precisa de uma flag de inicialização ou opção de proxy |
Então a primeira pergunta de depuração nunca é “a variável de ambiente está definida?”, mas sim “esse cliente a lê?”
Proxy de runtime: a configuração básica
Passe o proxy em tempo de execução, não em tempo de build, e referencie o gateway com o direcionamento codificado no nome de usuário:
docker run --rm \ -e HTTP_PROXY="http://${SHIFTER_USER}-country-us:${SHIFTER_PASS}@p.shifter.io:443" \ -e HTTPS_PROXY="http://${SHIFTER_USER}-country-us:${SHIFTER_PASS}@p.shifter.io:443" \ -e NO_PROXY="localhost,127.0.0.1,postgres,redis,.internal,169.254.169.254" \ my-scraper:latestDefina tanto maiúsculas quanto minúsculas (HTTP_PROXY e http_proxy) se você não tiver certeza sobre suas bibliotecas; as convenções variam, e algumas ferramentas leem apenas uma. Observe que HTTPS_PROXY ainda aponta para uma URL http://, e isso está correto: o esquema descreve como você se comunica com o proxy, e o HTTPS é tunelado através dele com CONNECT.
No Compose, mantenha os valores fora do próprio arquivo:
services: scraper: image: my-scraper:latest environment: HTTP_PROXY: "http://${SHIFTER_USER}-country-us:${SHIFTER_PASS}@p.shifter.io:443" HTTPS_PROXY: "http://${SHIFTER_USER}-country-us:${SHIFTER_PASS}@p.shifter.io:443" NO_PROXY: "localhost,127.0.0.1,postgres,redis,.internal" depends_on: [postgres, redis]As variáveis são interpoladas a partir do seu shell ou de um arquivo .env que não é commitado.
NO_PROXY: o passo que as pessoas pulam, e se arrependem
Esse é o que causa as falhas mais confusas. Uma vez que HTTP_PROXY está definido, todo cliente que o respeita envia tudo pelo proxy, incluindo chamadas ao seu banco de dados, seu cache, suas APIs internas, e endpoints de metadados de nuvem. Consequências: o tráfego interno sai da sua rede e volta (lento, e às vezes quebrado), health checks falham, e você consome banda cobrada por GB em tráfego que nunca deveria ter saído do host.
Sempre defina NO_PROXY para cobrir:
localhost,127.0.0.1,::1- Nomes de serviço do Compose/Kubernetes (
postgres,redis,api.default.svc.cluster.local) - Domínios internos e faixas privadas (
.internal,10.0.0.0/8) - Metadados de nuvem:
169.254.169.254
Duas ressalvas que vale a pena conhecer: a correspondência de NO_PROXY não é implementada de forma consistente entre bibliotecas (o suporte a CIDR em particular é irregular, e algumas fazem correspondência de sufixos enquanto outras precisam de um ponto inicial), então verifique em vez de assumir. E no Kubernetes, .svc.cluster.local e os CIDRs de pod/serviço também pertencem ao NO_PROXY.
Segredos: não grave credenciais na imagem
Credenciais de proxy são segredos. As regras:
Nunca as coloque em um Dockerfile via ENV ou ARG, ambos persistem nas camadas de imagem e o docker history vai imprimi-las alegremente. Qualquer um que possa fazer pull da imagem tem suas credenciais.
Injete-as em runtime. Para o Compose, um arquivo de env mantido fora do git; para orquestração, um repositório de segredos de verdade:
# Kubernetes: credenciais de um Secret, não do manifestoenv: - name: SHIFTER_USER valueFrom: secretKeyRef: { name: proxy-creds, key: username } - name: SHIFTER_PASS valueFrom: secretKeyRef: { name: proxy-creds, key: password }Então construa a URL do proxy dentro da aplicação a partir dessas duas variáveis, de forma que a string completa de credenciais nunca apareça em um manifesto, uma linha de log, ou docker inspect. Se você precisar do proxy durante o build (instalando pacotes), use segredos do BuildKit (--mount=type=secret) em vez de ARG, para que nada fique gravado em uma camada.
Além disso: limpe URLs de proxy dos logs. Um crash dump que imprime a configuração efetiva vai vazar user:pass@host diretamente para o seu agregador de logs.
Mapeando identidade para containers
É aqui que a arquitetura de containers encontra a arquitetura de proxy. Como o direcionamento vive no nome de usuário, cada container pode carregar sua própria identidade apenas recebendo uma variável de ambiente diferente, sem endpoints separados, sem listas de IP.
Dois padrões cobrem a maioria das necessidades:
Um container, uma geo. Execute workers por mercado variando a flag de país:
docker run -d -e HTTP_PROXY="http://${U}-country-us:${P}@p.shifter.io:443" scraper:latestdocker run -d -e HTTP_PROXY="http://${U}-country-de:${P}@p.shifter.io:443" scraper:latestUm container, uma sessão sticky. Dê a cada réplica um sid distinto para que ela mantenha seu próprio IP de saída, útil quando um container é responsável por um fluxo de múltiplas etapas:
services: worker: image: scraper:latest environment: # {{.Task.Slot}} dá a cada réplica do Swarm um id de sessão único e estável HTTP_PROXY: "http://${U}-country-us-sid-w{{.Task.Slot}}-ttl-600:${P}@p.shifter.io:443" deploy: replicas: 4Um alerta: uma variável de ambiente em nível de container é uma identidade estática durante toda a vida útil desse container. Se sua carga de trabalho precisa de rotação por requisição ou por unidade de trabalho, não simule isso reiniciando containers, defina o proxy no código da aplicação, onde você pode variar o sid por tarefa (o padrão descrito no post sobre load balancing). Variáveis de ambiente são a ferramenta certa para identidade grosseira, por container; código é a ferramenta certa para rotação refinada.
Clientes que ignoram o ambiente
Dois casos comuns que você vai encontrar dentro de containers:
O fetch/undici do Node não lê variáveis de ambiente de proxy. Configure-o explicitamente:
import { ProxyAgent, setGlobalDispatcher } from "undici";setGlobalDispatcher(new ProxyAgent(process.env.HTTP_PROXY));Navegadores headless também as ignoram, o Chromium precisa que o proxy seja passado na inicialização, e as credenciais tratadas pelo próprio mecanismo do framework (Playwright cobre os detalhes, incluindo por que credenciais inline falham no Chromium). O requests/httpx do Python respeitam as variáveis de ambiente, embora passar os proxies explicitamente seja mais claro (guia de Python).
Também vale notar: SOCKS5 via variáveis de ambiente é pouco confiável entre clientes em containers. Para a maioria das cargas de trabalho em containers, o proxy HTTP é a escolha padrão correta (tradeoffs do SOCKS5).
Verifique de dentro do container
Nunca assuma, verifique o IP de saída de dentro do container em execução:
docker exec -it my-scraper sh -c \ 'curl -s http://ip-api.com/json | head -c 200'# Espere um IP residencial no país alvo, não o IP do seu host.Se retornar o IP do seu host, o cliente não está respeitando as variáveis de ambiente (veja a tabela acima). Adicione isso como uma verificação de inicialização em staging para que um deploy mal configurado falhe de forma visível em vez de fazer scraping silenciosamente a partir do seu IP de datacenter, e verifique se NO_PROXY funciona confirmando que uma chamada de serviço interno não atravessa o proxy. Métodos de medição mais amplos estão em como testar velocidade, taxa de sucesso e precisão de localização de proxy.
Armadilhas de container que consomem uma tarde
localhostsignifica o container. Um proxy no host não é acessível em127.0.0.1de dentro do container; usehost.docker.internal(Docker Desktop) ou o endereço de rede do host.- Proxies de build-time e runtime são diferentes. Definir um não define o outro.
- Mudanças de ambiente precisam de recriação. Editar o ambiente no Compose exige
up --force-recreate, não um restart. - O local de resolução de DNS varia. Alguns clientes resolvem localmente, outros deixam o proxy resolver. Se os resultados sensíveis à geolocalização parecerem errados, esse é um suspeito.
- As imagens podem não ter certificados de CA. Bases slim/alpine às vezes precisam do
ca-certificatesinstalado para que o HTTPS através do proxy valide corretamente. - A cobrança por GB é por container. Dez réplicas fazendo pull de páginas completas multiplicam sua banda por dez, cortar custos de banda se aplica por réplica.
Perguntas frequentes
Por que meu container não está usando o proxy mesmo com HTTP_PROXY definido?
Porque HTTP_PROXY é uma convenção, não roteamento. A biblioteca precisa respeitá-la. O fetch do Node e navegadores headless não respeitam; curl, Python requests, e o transporte padrão do Go sim. Verifique seu cliente primeiro, depois confirme o IP de saída de dentro do container.
Devo definir o proxy no Dockerfile?
Não. Valores de ENV/ARG persistem nas camadas de imagem e aparecem no docker history, vazando credenciais para qualquer um que possa fazer pull da imagem. Injete em runtime via variáveis de ambiente de um repositório de segredos, e use segredos do BuildKit se precisar de um proxy durante o build.
Como faço para impedir que o tráfego interno passe pelo proxy?
Defina NO_PROXY com localhost, os nomes dos seus serviços, domínios internos, faixas privadas, e 169.254.169.254. Caso contrário, o tráfego de banco de dados e de health check é tunelado por uma saída residencial, o que é lento, frágil, e cobrado.
Cada container pode ter um IP ou país diferente? Sim. O direcionamento é codificado no nome de usuário do proxy, então uma variável de ambiente diferente por container dá a cada um seu próprio país ou sessão sticky, sem endpoints extras. Para rotação por requisição, defina o proxy no código da aplicação.
O proxy funciona para docker build?
Apenas se você configurar o proxy de build/daemon separadamente (~/.docker/config.json ou build args). As variáveis de ambiente do container em runtime não afetam os builds, e as credenciais de build-time não devem ser passadas como ARG.
Conclusão
A maioria dos problemas de proxy no Docker se resume a quatro coisas. Saiba onde a configuração se aplica (build vs runtime, e quais clientes de fato respeitam variáveis de ambiente), defina NO_PROXY para que o tráfego interno permaneça interno, mantenha credenciais fora das camadas de imagem e injete-as em runtime, e decida deliberadamente como a identidade é mapeada para os containers (variáveis de ambiente para identidade grosseira por container, código de aplicação para rotação por requisição). Depois verifique o IP de saída de dentro do container em vez de confiar na configuração.
Acertando isso, a coleta em containers se torna monótona da melhor forma possível. O gateway residencial ajuda aqui porque o direcionamento viaja no nome de usuário: um único endpoint, e a geo ou sessão de qualquer container é apenas uma variável de ambiente diferente. A qualidade do pool ainda determina com que frequência você está tentando de novo (reputação de IP), e a página de preços tem os planos por GB, vale lembrar que a banda escala com o número de réplicas.