1. Retomando: de imagens bem construídas a containers que conversam entre si
Os artigos anteriores desta série cobriram como criar imagens eficientes e rodar containers isolados. Mas uma aplicação real raramente é um único container: normalmente há uma API, um banco de dados, um cache, talvez uma fila de mensagens — cada um em seu próprio container, precisando se comunicar. E containers, por padrão, são efêmeros: qualquer dado escrito dentro deles some quando são removidos. Este artigo cobre as duas peças que resolvem isso: redes (comunicação entre containers) e volumes (persistência de dados).
2. O problema do isolamento de rede por padrão
Cada container recebe seu próprio namespace de rede, isolado dos demais e do host. Isso é uma característica de segurança, não um bug — mas significa que dois containers rodados de forma independente não conseguem se encontrar automaticamente:
docker run -d --name api minha-api
docker run -d --name banco postgres
De dentro do container api, tentar acessar banco por esse nome simplesmente falha — cada container, isolado, só enxerga localhost como a si mesmo. A solução do Docker para isso é criar uma rede e conectar ambos os containers a ela.
3. Redes definidas pelo usuário (User-Defined Networks)
docker network create minha-rede
docker run -d --name banco --network minha-rede postgres
docker run -d --name api --network minha-rede minha-api
A partir daqui, dentro do container api, o hostname banco resolve automaticamente para o IP do container banco — o Docker roda um DNS interno para qualquer rede definida pelo usuário, resolvendo containers pelo nome (ou pelo alias definido com --network-alias, se houver mais de um). Isso é o motivo pelo qual strings de conexão em aplicações containerizadas costumam usar o nome do serviço em vez de um IP fixo:
DATABASE_URL=postgresql://usuario:senha@banco:5432/meudb
Comandos úteis para inspecionar redes:
docker network ls # lista todas as redes
docker network inspect minha-rede # detalhes: containers conectados, subnet, gateway
docker network connect minha-rede outro-container # conecta um container já em execução
docker network disconnect minha-rede outro-container
4. Os drivers de rede mais comuns
- bridge (padrão): cada container recebe um IP interno numa sub-rede privada isolada; é o driver usado nos exemplos acima e o adequado para a grande maioria dos casos de uma única máquina.
- host: o container compartilha diretamente a stack de rede do host, sem isolamento — usado quando a latência extra do NAT do bridge é inaceitável, ao custo de perder o isolamento de portas.
- none: desabilita rede completamente — útil para containers que só processam dados locais e não devem ter acesso externo algum, por segurança.
- overlay: conecta containers rodando em hosts diferentes, usado em clusters multi-máquina (Docker Swarm, e conceito similar ao que o Kubernetes resolve à sua própria maneira) — fora do escopo de uma única máquina, mas bom saber que existe.
Para a maioria dos projetos rodando em uma única máquina — o caso coberto por esta série — bridge com redes definidas pelo usuário é a resposta certa quase sempre.
5. Volumes: persistindo dados além da vida do container
Por padrão, tudo que um processo escreve dentro de um container vive na camada gravável dele — e essa camada é destruída junto com o container quando ele é removido (docker rm). Para um banco de dados, isso é inaceitável: perder todos os dados a cada docker rm banco não é uma opção. A solução são volumes, que existem fora do ciclo de vida de qualquer container específico.
docker volume create dados-banco
docker run -d --name banco \
-v dados-banco:/var/lib/postgresql/data \
postgres
Mesmo removendo e recriando o container banco, os dados em dados-banco sobrevivem — um novo container apontando para o mesmo volume enxerga exatamente os mesmos dados de antes:
docker stop banco && docker rm banco
docker run -d --name banco -v dados-banco:/var/lib/postgresql/data postgres
# os dados anteriores continuam lá
Comandos de gerenciamento:
docker volume ls # lista volumes
docker volume inspect dados-banco # onde o volume vive no host, entre outros metadados
docker volume rm dados-banco # remove (falha se algum container ainda o usa)
docker volume prune # remove todos os volumes não usados por nenhum container
6. Volumes nomeados vs bind mounts
Existem duas formas de "montar" algo do host dentro de um container, e a diferença importa:
-
Volume nomeado (
-v dados-banco:/caminho): gerenciado inteiramente pelo Docker, que decide onde no host os dados ficam de fato armazenados. É a opção certa para dados que uma aplicação gera e consome (bancos de dados, filas, caches persistentes) — portátil entre ambientes, porque não depende de um caminho específico do host. -
Bind mount (
-v /caminho/no/host:/caminho/no/container): aponta diretamente para um caminho existente do sistema de arquivos do host. É a opção certa para desenvolvimento local — montar o código-fonte do projeto dentro do container para ver mudanças refletidas sem rebuildar a imagem a cada alteração:
# Desenvolvimento: código do host refletido ao vivo dentro do container
docker run -d -v $(pwd)/src:/app/src -p 3000:3000 minha-app-dev
Bind mounts não são recomendados para dados de produção porque acoplam o container a um caminho específico do host, o que quebra a portabilidade que o Docker existe para garantir.
7. Um cenário completo: API + banco comunicando-se com dados persistentes
Juntando rede e volume no mesmo exemplo — uma API e um Postgres, isolados em rede própria, com dados que sobrevivem a reinicializações:
docker network create app-rede
docker volume create pg-dados
docker run -d --name banco \
--network app-rede \
-v pg-dados:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=segredo \
postgres:16
docker run -d --name api \
--network app-rede \
-p 8000:8000 \
-e DATABASE_URL=postgresql://postgres:segredo@banco:5432/postgres \
minha-api:1.0
Esse é exatamente o tipo de configuração que, no próximo artigo, o Docker Compose substitui por um único arquivo declarativo — em vez de dois comandos docker run longos para lembrar (e digitar) toda vez.
8. Conclusão e próximos passos
Redes definidas pelo usuário resolvem a comunicação entre containers via DNS interno pelo nome, e volumes resolvem a persistência de dados além do ciclo de vida de qualquer container individual — as duas peças que faltavam para modelar uma aplicação real com múltiplos serviços. No próximo artigo, o Docker Compose entra em cena para descrever tudo isso — rede, volumes e múltiplos containers — em um único arquivo declarativo, versionável junto com o código.
Imagem de capa: Logo oficial do Docker — Wikimedia Commons, fonte: docker.com/company/newsroom/media-resources
Referências:
Top comments (0)