DEV Community

Cover image for Docker - redes e volumes na prática
Rafael Dutra for apsis-cc

Posted on

Docker - redes e volumes na prática

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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á
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Docker Documentation — Networking Overview
  2. Docker Documentation — Volumes

Top comments (0)