Por que dois containers no mesmo host não se enxergam por padrão

Suba o Postgres e sua API com um docker run simples, e a API não consegue alcançar o banco de dados, mesmo com os dois containers rodando na mesma máquina.

1
2
docker run -d --name db postgres:16
docker run -d --name api myapp

Os dois caem na bridge padrão do Docker, aquela em que todo container Docker entra a menos que você diga o contrário. Eles recebem um endereço IP. O que não recebem é uma forma de se encontrarem pelo nome — a API teria que ter o IP interno do banco fixado no código, um endereço que muda a cada reinício do container.

Uma rede Docker resolve isso. É um switch virtual ao qual os containers se conectam, com sua própria faixa de IP e seu próprio DNS. Ao criar uma, os nomes dos containers viram hostnames, e você decide quem enxerga quem.

Qual driver de rede usar: bridge, host ou overlay

O Docker traz vários drivers de rede embutidos. Três deles cobrem quase tudo que você vai encontrar em um único host ou em um swarm:

DriverEscopoContainers têm IP próprioDNS por nomeUso típico
bridge (padrão)um único hostsimnão (só IP)o que você tem sem a flag --network
bridge (user-defined)um único hostsimsima escolha padrão para desenvolvimento local e deploys de host único
hostum único hostnão — compartilha a pilha de rede do hostn/aserviços de alto throughput, ferramentas que precisam ver a rede real do host
overlayvários hosts (swarm)simsimserviços distribuídos entre mais de um host Docker

Também existe o none, que dá ao container apenas uma interface de loopback e nada mais. Útil para um job em lote que não precisa de nenhum acesso à rede.

Todo docker run novo cai na bridge padrão. Funciona, mas ela só roteia por IP; a antiga flag --link existia para contornar a falta de nomes, e o Docker a marca como legada. Uma bridge user-defined faz o mesmo trabalho com DNS embutido. É essa que você quer.

Como criar uma rede Docker e conectar containers a ela

1
docker network create app-net

Isso é uma bridge privada com sub-rede própria, isolada da bridge padrão e de qualquer outra rede user-defined que você criar. Conecte os containers na hora de subir:

1
2
docker run -d --name db --network app-net -e POSTGRES_PASSWORD=devpass postgres:16
docker run -d --name api --network app-net -p 8080:8080 myapp

Dentro do api, conectar em db:5432 funciona. Sem busca de IP, sem malabarismo com variáveis de ambiente: o servidor DNS embutido do Docker resolve o nome do container db para o endereço atual dele em app-net, e atualiza esse mapeamento quando o container reinicia com um IP novo. O Docker Compose roda sobre esse mesmo mecanismo. docker compose up cria uma rede para o stack, e cada serviço alcança os outros pelo nome do serviço.

Você também pode conectar um container que já está rodando, sem reiniciá-lo:

1
2
docker network connect app-net some-container
docker network disconnect app-net some-container

Um container pode estar em mais de uma rede ao mesmo tempo. É assim que você dá a um serviço uma rede exposta publicamente e uma privada, compartilhada só com os containers de backend por trás dele.

Por que publicar uma porta não é como dois containers se comunicam

-p 8080:8080 mapeia a porta 8080 do host para a do container, o que permite acessar a API pelo navegador em localhost:8080. Isso não diz nada sobre se api consegue alcançar db. O tráfego entre containers na mesma rede user-defined fica interno, portas publicadas ou não.

Publique só as portas que algo fora do host Docker precisa alcançar. Um banco que só conversa com a API não precisa de -p 5432:5432: deixe essa porta sem publicar, e nem o host nem a internet conseguem se conectar diretamente ao banco. api continua alcançando o banco normalmente, porque os dois compartilham app-net.

Quando usar –network host, e quando não usar

1
docker run --rm -d --network host --name my_nginx nginx

Com --network host, o container pula seu próprio namespace de rede e se vincula direto às interfaces do host. Sem NAT, sem bridge, sem mapeamento de portas: -p e -P são ignorados, e o Docker exibe um aviso se você os passar mesmo assim. Isso reduz um pouco a sobrecarga por pacote e dá ao container a visão real das interfaces de rede do host — o que interessa a ferramentas de monitoramento de rede e a softwares que precisam de uma faixa grande e contígua de portas.

Também tira o isolamento que uma bridge oferece. O container pode usar qualquer porta livre do host, e enxerga tudo que passa pela rede do host. No Linux, isso é totalmente suportado. O Docker Desktop para Mac e Windows exige ativação explícita (disponível a partir do Docker Desktop 4.34), porque a rede da VM do Docker não é a do host. Containers Windows não suportam isso de jeito nenhum. Para a maioria dos serviços, uma bridge user-defined com portas publicadas faz o mesmo trabalho com menos exposição.

Quando você precisa de uma rede overlay em vez de uma bridge

Redes bridge e host param na borda de um único host Docker. Rode Docker Swarm em várias máquinas, e um serviço no host A precisa de uma rede overlay para alcançar um serviço no host B:

1
docker network create -d overlay --attachable app-overlay

Uma overlay encapsula o tráfego dos containers e o roteia entre os daemons Docker de cada nó do swarm, então containers em máquinas físicas ou virtuais diferentes se encontram pelo nome exatamente como fariam numa bridge. A flag --attachable permite que containers standalone também entrem nela; sem ela, só os serviços do swarm conseguem se conectar. Se você não roda swarm ou outro orquestrador multi-host, pule esse driver. Uma rede bridge de host único cobre stacks do Compose e ambientes locais.

Como inspecionar e depurar uma rede Docker

1
docker network ls

Todas as redes do host, incluindo bridge, host e none, que o Docker sempre cria.

1
docker network inspect app-net

A sub-rede, o gateway e cada container conectado no momento com seu IP naquela rede. É o primeiro lugar a olhar quando um container não consegue alcançar outro. Leia a seção Containers: se algum dos dois estiver faltando, ele não está nessa rede, e o docker network connect o coloca lá.

Para testar a resolução DNS de dentro de um container em execução:

1
docker exec api getent hosts db

Um IP de volta significa que a resolução de nomes funciona e o problema está na aplicação — porta errada, ou o serviço ainda não está escutando. Nada de volta significa que os dois containers não estão na mesma rede. Confirme com docker network inspect, ou pergunte a cada container a que ele está conectado:

1
docker inspect <container> --format '{{ .NetworkSettings.Networks }}'

Uma rede por stack, com todo container nela

A bridge padrão é o que você ganha de graça. Não é sobre ela que você deve construir. Rode docker network create <app>-net para cada stack de aplicação, conecte nela todo container que esse stack precisar, e reserve --network host para o serviço raro que realmente precisa da pilha de rede do host.

Quando você passa a gerenciar mais de um par de containers na mão, pare de fazer isso na mão. O Docker Compose cria a rede e conecta os containers por você a partir de um único arquivo YAML. Os volumes Docker cobrem a outra metade de sobreviver a um reinício: os dados que esses containers escrevem.

Artigos relacionados