Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

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.

Rodolfo Mori5 min de leitura

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:

bash
docker network create dc-cantina-net
f8f804bbe1a78d81dbdcc8cb65aa3a2520974f67ac4b0aa3dbb9eae6a94ed6d7

O 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:

bash
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')"
d811fa234736b401d012ba52ddcbc02ff49b5669a831eb16c6233dff72e94a99

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:

bash
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)"
cardápio: sopa e pão

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:

bash
docker run --rm --network dc-cantina-net node:24-alpine node -e "require('node:dns').lookup('dc-cardapio-api',(erro,endereco)=>console.log(endereco))"
172.28.0.2

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:

bash
docker network inspect dc-cantina-net --format 'Driver={{.Driver}} | Escopo={{.Scope}} | Contêineres={{len .Containers}}'
Driver=bridge | Escopo=local | Contêineres=1

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:

bash
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: 127.0.0.1:3000

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:

bash
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: dc-cardapio-api

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:

  • ENOTFOUND aponta primeiro para nome, rede ou DNS;
  • ECONNREFUSED aponta 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:

bash
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')"
dc-cardapio-api 8d8f5278d3d2e5557dd344d233875a844d49d2c3b4c52e5fdff73324e2fa7551

A ordem é host:contêiner: 3088 no host encaminha para 3000 no contêiner. Confirme o mapeamento:

bash
docker port dc-cardapio-api
3000/tcp -> 127.0.0.1:3088

Agora o host consegue chamar a API:

bash
curl -sS http://127.0.0.1:3088
cardápio publicado

Publicar 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:

bash
docker rm -f dc-cardapio-api
docker network rm dc-cantina-net
dc-cardapio-api dc-cantina-net

O 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.

  • docker
  • redes docker
  • bridge
  • dns
  • localhost
  • portas

Perguntas frequentes

Contêineres precisam publicar portas para conversar entre si?
Não quando estão na mesma rede Docker. Eles usam a porta interna do serviço e o nome do contêiner ou serviço. Publicar porta serve para receber conexões vindas do host ou de fora daquela rede.
O endereço IP de um contêiner é fixo?
Não deve ser tratado como fixo. Ao recriar o contêiner, Docker pode atribuir outro IP. Use o DNS interno e o nome estável do serviço.
O que significa host.docker.internal?
É um nome especial disponibilizado pelo Docker Desktop para um contêiner alcançar um serviço do host. Ele não substitui o nome de outro contêiner e seu suporte em Docker Engine Linux depende da configuração usada.
Posso colocar um contêiner em mais de uma rede?
Sim. Isso permite que uma API fale com a rede do proxy e com a rede do banco, enquanto o banco continua fora da rede pública. Cada conexão deve existir por uma necessidade concreta.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. Docker Docs — Networking overview — docs.docker.com
  2. Docker Docs — Bridge network driver — docs.docker.com
  3. Docker Docs — Published ports — docs.docker.com
  4. Docker Docs — Networking in Compose — docs.docker.com

Continue por aqui