Docker Compose: orquestrando múltiplos containers
1. Introdução ao Docker Compose no contexto DevOps
1.1. O que é Docker Compose e por que ele é essencial para orquestração local
Docker Compose é uma ferramenta que permite definir e executar aplicações multi-container com um único arquivo YAML. No contexto DevOps, ele se torna indispensável para reproduzir ambientes complexos localmente, garantindo que desenvolvedores, testadores e pipelines de CI/CD utilizem exatamente a mesma configuração de serviços, redes e volumes. Com um simples docker-compose up, toda a infraestrutura da aplicação é levantada de forma consistente.
1.2. Diferença entre Docker Compose e orquestradores completos
Enquanto Docker Compose é ideal para ambientes de desenvolvimento e testes locais, orquestradores como Kubernetes e Docker Swarm são projetados para produção em larga escala. Compose opera em um único host e não oferece recursos como auto-scaling, rolling updates avançados ou balanceamento de carga distribuído. Já Kubernetes gerencia clusters multi-host com alta disponibilidade, mas com complexidade operacional muito maior.
1.3. Casos de uso típicos
- Ambientes de desenvolvimento: equipes podem subir stacks completas (app + banco + cache + fila) em segundos.
- Testes integrados: pipelines CI sobem serviços dependentes para execução de testes de integração.
- Demonstrações e prototipagem: compartilhar um
docker-compose.ymlpermite que qualquer pessoa reproduza o ambiente.
2. Estrutura fundamental do arquivo docker-compose.yml
2.1. Anatomia de um Compose file
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "80:80"
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
volumes:
pgdata:
networks:
default:
driver: bridge
A estrutura básica contém: version (define a sintaxe), services (containers), networks (comunicação) e volumes (persistência).
2.2. Definindo serviços
Cada serviço pode ser configurado com:
services:
app:
build: ./app # Dockerfile local
image: myapp:latest # ou imagem pré-existente
ports:
- "3000:3000"
environment:
- DB_HOST=db
- DB_PORT=5432
depends_on:
- db
volumes:
- ./app:/usr/src/app
2.3. Boas práticas de versionamento
- Sempre especificar a versão do Compose (atualmente
3.8ou3.9). - Manter o YAML indentado corretamente (2 espaços).
- Usar variáveis de ambiente para valores sensíveis.
- Versionar o
docker-compose.ymljunto com o código da aplicação.
3. Gerenciamento de dependências e ordem de inicialização
3.1. depends_on básico
services:
app:
depends_on:
- db
- redis
Isso garante que db e redis iniciem antes de app, mas não espera que estejam prontos para receber conexões.
3.2. Healthchecks customizados
services:
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
app:
depends_on:
db:
condition: service_healthy
Com condition: service_healthy, o serviço app só inicia quando o PostgreSQL realmente estiver aceitando conexões.
3.3. Estratégias de retry com wait-for-it
Para serviços que não suportam healthcheck nativo, use scripts como wait-for-it.sh no entrypoint:
services:
app:
entrypoint: ["./wait-for-it.sh", "db:5432", "--", "npm", "start"]
4. Redes e comunicação entre containers no Compose
4.1. Rede padrão e DNS interno
Por padrão, o Compose cria uma rede bridge onde todos os serviços se comunicam pelo nome do serviço. Exemplo: o serviço app acessa o banco via db:5432.
4.2. Redes customizadas para isolamento
services:
frontend:
networks:
- front-net
backend:
networks:
- back-net
- front-net
db:
networks:
- back-net
networks:
front-net:
back-net:
Isso permite que db seja inacessível ao frontend, aumentando a segurança.
4.3. Expondo portas vs comunicação interna
- Use
portsapenas para serviços que precisam ser acessados do host (ex:80:80). - Para comunicação interna, confie no DNS interno do Compose (não exponha portas desnecessárias).
5. Volumes e persistência de dados com Compose
5.1. Volumes nomeados vs bind mounts
volumes:
dbdata: # volume nomeado (gerenciado pelo Docker)
services:
db:
volumes:
- dbdata:/var/lib/postgresql/data # persistência segura
app:
volumes:
- ./src:/app/src # bind mount para desenvolvimento
- Volumes nomeados: ideais para dados de produção (banco, logs).
- Bind mounts: úteis para desenvolvimento (código sincronizado).
5.2. Compartilhamento entre serviços
volumes:
shared-cache:
services:
app:
volumes:
- shared-cache:/cache
worker:
volumes:
- shared-cache:/cache
5.3. Estratégias para dados críticos
- Banco de dados: sempre usar volume nomeado.
- Logs: bind mount para diretório de logs no host ou volume nomeado.
- Uploads: volume nomeado para evitar perda ao recriar containers.
6. Variáveis de ambiente e configuração por ambiente
6.1. Arquivo .env e substituição
Crie um arquivo .env na raiz:
DB_PASSWORD=minhasenha
APP_PORT=3000
No docker-compose.yml:
services:
app:
environment:
- DB_PASSWORD=${DB_PASSWORD}
- APP_PORT=${APP_PORT}
6.2. Perfis (profiles) para diferentes cenários
services:
db:
image: postgres:15
app-dev:
profiles: ["dev"]
build: ./app
app-prod:
profiles: ["prod"]
image: myapp:latest
Execute com: docker-compose --profile dev up
6.3. Segredos e boas práticas
- Nunca hardcode senhas no YAML.
- Use arquivos
.envignorados pelo Git (.gitignore). - Para produção, considere Docker Secrets ou ferramentas como Vault.
7. Exemplo prático: aplicação web com banco e cache
7.1. Serviços definidos
version: '3.8'
services:
app:
build: ./app
ports:
- "3000:3000"
environment:
- DB_HOST=db
- DB_PORT=5432
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- ./app:/usr/src/app
networks:
- app-net
db:
image: postgres:15-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 5s
timeout: 5s
retries: 5
networks:
- app-net
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-net
volumes:
pgdata:
redisdata:
networks:
app-net:
driver: bridge
7.2. Comandos essenciais
# Iniciar todos os serviços
docker-compose up -d
# Ver logs em tempo real
docker-compose logs -f
# Executar comando em um serviço
docker-compose exec app npm test
# Listar serviços em execução
docker-compose ps
# Parar e remover tudo
docker-compose down -v
8. Do Compose ao Kubernetes: migração e similaridades
8.1. Mapeamento de conceitos
| Docker Compose | Kubernetes |
|---|---|
| Service | Deployment + Service |
| Network | NetworkPolicy |
| Volume | PersistentVolumeClaim |
| depends_on | Init Containers |
| ports | Service NodePort/LoadBalancer |
8.2. Ferramentas de conversão
O kompose converte automaticamente:
kompose convert -f docker-compose.yml
Isso gera arquivos YAML do Kubernetes prontos para uso com kubectl apply.
8.3. Limitações do Compose para produção
- Escalabilidade: não suporta auto-scaling horizontal.
- Alta disponibilidade: opera em um único host.
- Rolling updates: não gerencia atualizações graduais.
- Monitoramento: sem integração nativa com métricas e alertas.
Migre para Kubernetes quando precisar de resiliência multi-host, escalabilidade automática e gerenciamento avançado de tráfego.
Referências
- Documentação oficial do Docker Compose — Guia completo de referência, instalação e exemplos de uso do Docker Compose.
- Compose Specification — Especificação oficial do formato docker-compose.yml, mantida pela comunidade.
- Kompose: conversão de Compose para Kubernetes — Ferramenta oficial para converter arquivos docker-compose.yml em deployments Kubernetes.
- Docker Compose Healthchecks: guia prático — Tutorial completo sobre healthchecks em serviços do Compose, com exemplos práticos.
- Boas práticas para Docker Compose em produção — Artigo técnico sobre como adaptar Compose para cenários produtivos e limitações conhecidas.
- Usando variáveis de ambiente no Docker Compose — Guia detalhado sobre substituição de variáveis, arquivos .env e segredos no Compose.