Redes Docker: conectar contêineres por nome e porta
Crie uma rede bridge, conecte contêineres pelo DNS interno, entenda por que localhost falha e publique somente a porta necessária no host.
Uma rede Docker permite que contêineres isolados conversem por nomes
estáveis. Nesta lição você vai colocar uma API e um cliente na mesma rede,
observar o DNS interno e reproduzir dois erros diferentes: localhost recusado
e nome inexistente fora da rede.
Volumes resolvem permanência; redes resolvem comunicação. Se essa separação ainda estiver nebulosa, revise a prova de persistência em volumes Docker antes de ligar os serviços.
O ramal depende do prédio em que você entrou
Imagine duas empresas em prédios diferentes. Dentro de cada uma, a telefonista conhece ramais pelo nome: “cozinha”, “recepção”, “financeiro”. Um visitante fora daquela central não consegue discar apenas “cozinha”. E quando alguém diz “minha mesa”, está falando do lugar onde a própria pessoa está.
Nessa comparação, a rede definida pelo usuário é a central privada, o DNS do
Docker é a lista de ramais e o nome do contêiner é o ramal. localhost significa
“este próprio contêiner”, assim como “minha mesa” muda conforme quem fala.
Voltando ao comportamento técnico: cada contêiner tem sua própria interface de
loopback. Uma API em outro contêiner nunca está no seu 127.0.0.1. Na mesma rede
definida pelo usuário, o DNS embutido resolve nomes e aliases para o IP atual. A
aplicação deve guardar o nome, não o IP que pode mudar numa recriação.
Uma bridge definida por você cria o limite
Docker possui drivers de rede. O driver bridge conecta contêineres no mesmo daemon e isola essa comunicação de outras bridges. Crie uma rede para a cantina:
docker network create dc-cantina-netO ID será diferente no seu ambiente. Como nenhum --driver foi informado,
Docker usa bridge para uma rede local definida pelo usuário.
Inicie a API nessa rede, ainda sem publicar porta no host:
docker run -d --name dc-cardapio-api --network dc-cantina-net node:24-alpine node -e "require('node:http').createServer((req,res)=>res.end('cardápio: sopa e pão')).listen(3000,'0.0.0.0')"A aplicação escuta na porta 3000 do contêiner. Sem -p, essa porta não foi
publicada no host. Isso não impede outro membro da mesma rede de usá-la.
O nome vira endereço dentro da rede
Crie um cliente descartável na mesma bridge. Ele chama o nome
dc-cardapio-api, não um IP:
docker run --rm --network dc-cantina-net node:24-alpine node -e "fetch('http://dc-cardapio-api:3000').then(r=>r.text()).then(console.log)"Não houve -p em nenhum dos dois comandos. O tráfego saiu do cliente e chegou
diretamente à porta 3000 da API pela rede Docker.
Veja o endereço que o DNS embutido devolveu naquele instante:
docker run --rm --network dc-cantina-net node:24-alpine node -e "require('node:dns').lookup('dc-cardapio-api',(erro,endereco)=>console.log(endereco))"Seu IP provavelmente será outro. Não copie esse número para configuração. O nome sobrevive à recriação; o endereço é uma decisão interna da rede.
Inspecione o mecanismo e conte membros atuais:
docker network inspect dc-cantina-net --format 'Driver={{.Driver}} | Escopo={{.Scope}} | Contêineres={{len .Containers}}'O cliente usou --rm e já terminou, então resta apenas a API. Escopo=local
indica que essa bridge vive num único daemon. Redes entre vários hosts usam
outro mecanismo, como overlay em um cluster Swarm.
Erro real 1: localhost volta para o próprio cliente
Repita o cliente, mas troque o hostname por 127.0.0.1:
docker run --rm --network dc-cantina-net node:24-alpine node -e "fetch('http://127.0.0.1:3000').catch(error=>{console.error(error.cause.code+': '+error.cause.address+':'+error.cause.port);process.exit(1)})"ECONNREFUSED significa que o endereço foi alcançado, mas não havia processo
escutando naquela porta. O cliente procurou a porta 3000 dele mesmo. A API
continua saudável em outro contêiner.
A correção é voltar ao nome dc-cardapio-api. Em Compose, esse nome normalmente
será o nome do serviço, como api ou db.
Erro real 2: o ramal não existe fora da central
Agora mantenha o nome correto, mas não conecte o cliente à rede:
docker run --rm node:24-alpine node -e "fetch('http://dc-cardapio-api:3000').catch(error=>{console.error(error.cause.code+': '+error.cause.hostname);process.exit(1)})"ENOTFOUND é falha de resolução de nome. Diferentemente do erro anterior, o
cliente nem chegou a tentar a porta: naquela rede padrão, o DNS não conhece o
ramal dc-cardapio-api.
Essa distinção encurta diagnóstico:
ENOTFOUNDaponta primeiro para nome, rede ou DNS;ECONNREFUSEDaponta para endereço alcançado sem processo na porta;- timeout aponta para pacote sem resposta e pode envolver rota ou firewall.
Depois da rede, ainda vale conferir o protocolo e os status HTTP quando a conexão acontece e a aplicação devolve uma resposta de erro.
Publicar porta abre um caminho para o host
Contêineres na mesma bridge já enxergam as portas uns dos outros. -p atende
outro caso: conexões iniciadas pelo host ou por fora da rede. Recrie a API com
uma publicação limitada ao loopback:
docker rm -f dc-cardapio-api
docker run -d --name dc-cardapio-api --network dc-cantina-net -p 127.0.0.1:3088:3000 node:24-alpine node -e "require('node:http').createServer((req,res)=>res.end('cardápio publicado')).listen(3000,'0.0.0.0')"A ordem é host:contêiner: 3088 no host encaminha para 3000 no contêiner.
Confirme o mapeamento:
docker port dc-cardapio-apiAgora o host consegue chamar a API:
curl -sS http://127.0.0.1:3088Publicar em 127.0.0.1 restringe o acesso à máquina local. Omitir o endereço
costuma publicar em todas as interfaces do host, o que pode expor o serviço à
rede. Banco que só a API usa normalmente não precisa de porta pública.
Compose vai criar essa rede automaticamente
Gerenciar cada docker run funciona para enxergar o mecanismo. Quando o projeto
tem API, banco e worker, repetir nomes, volumes, healthchecks e portas à mão
vira fonte de divergência. Docker Compose
descreve o conjunto e cria uma bridge padrão na qual cada serviço é encontrado
pelo próprio nome.
Isso não torna redes irrelevantes. Compose automatiza a criação; o modelo
continua sendo DNS, porta interna e limites entre redes. Quando DATABASE_URL
usa localhost dentro da API, o erro continua o mesmo.
Missão: isolar e depois conectar
Crie duas bridges, dc-frente e dc-dados. Coloque uma API apenas em
dc-dados e um cliente apenas em dc-frente. Primeiro confirme que o cliente
recebe ENOTFOUND ao buscar o nome da API. Depois conecte a API também à rede
dc-frente e repita o mesmo cliente.
O critério de sucesso é passar de ENOTFOUND para a resposta HTTP sem publicar
porta no host e sem copiar IP. A mudança permitida foi a associação à rede; nome
e porta permaneceram iguais.
Ao terminar, remova os contêineres e somente as duas redes da missão. Para o laboratório deste artigo, os alvos exatos são:
docker rm -f dc-cardapio-api
docker network rm dc-cantina-netO guia de Docker reúne o modelo completo. A última lição vai juntar imagem, volume, rede e verificação de saúde num único arquivo.
Perguntas frequentes
Contêineres precisam publicar portas para conversar entre si?
O endereço IP de um contêiner é fixo?
O que significa host.docker.internal?
Posso colocar um contêiner em mais de uma rede?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Docker Engine 29.5.3, Docker Compose 5.1.4 e Node 24.19.0 (node:24-alpine) no macOS ARM64, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- Docker Docs — Networking overview — docs.docker.com
- Docker Docs — Bridge network driver — docs.docker.com
- Docker Docs — Published ports — docs.docker.com
- Docker Docs — Networking in Compose — docs.docker.com


