Docker Compose
Nesta aula, você aprenderá a orquestrar múltiplos containers com Docker Compose, desde a criação do arquivo docker-compose.yml até a definição de serviços e dependências. Exploraremos casos de uso reais e práticas recomendadas para ambientes de desenvolvimento e produção.
Docker Compose é uma ferramenta essencial para orquestrar aplicações multi-container. Em vez de gerenciar cada container individualmente com comandos docker run, o Compose permite definir toda a infraestrutura em um único arquivo YAML, chamado docker-compose.yml, e gerenciá-la com comandos simples como docker-compose up e docker-compose down. Esta aula aborda desde os conceitos fundamentais até casos de uso avançados, capacitando você a estruturar ambientes complexos com eficiência.
Dominar o Docker Compose é crucial para qualquer profissional de DevOps, pois simplifica o desenvolvimento local, padroniza ambientes e facilita a integração contínua. Vamos explorar como definir serviços, conectar containers, gerenciar dependências e aplicar boas práticas para criar soluções robustas e escaláveis.
Multi-container
No mundo real, aplicações raramente são compostas por um único container. Um sistema web típico pode incluir um servidor web, um backend, um banco de dados, um cache, um fila de mensagens, entre outros. Gerenciar cada um separadamente é trabalhoso e propenso a erros, especialmente quando precisamos configurar redes, volumes e variáveis de ambiente.
O Docker Compose resolve esse problema permitindo que você defina todos os serviços em um único arquivo. Ele cria uma rede padrão para os containers, permitindo que eles se comuniquem por nome do serviço, e gerencia o ciclo de vida de todos juntos. Isso facilita a replicação de ambientes, o versionamento da infraestrutura e a colaboração entre equipes.
Por exemplo, um simples stack com Nginx, Node.js e PostgreSQL pode ser definido e iniciado com apenas um comando. Sem o Compose, você precisaria executar três comandos docker run com várias flags, além de configurar manualmente a rede. Com o Compose, tudo fica declarativo e versionável.
# Sem Docker Compose
sudo docker run -d --name db -e POSTGRES_USER=user -e POSTGRES_PASSWORD=pass postgres:15
docker network create mynet
docker network connect mynet db
docker run -d --name backend --network mynet -e DB_HOST=db mybackend:latest
docker run -d --name frontend --network mynet -p 80:80 nginx:alpine
# Com Docker Compose
# docker-compose.yml define tudo, e então:
docker-compose up -ddocker-compose.yml
O arquivo docker-compose.yml é o coração do Docker Compose. Ele segue a especificação YAML e contém a definição dos serviços, redes, volumes e outras configurações. A estrutura básica começa com a chave version (embora em versões recentes seja opcional) e a chave services.
Dentro de services, cada serviço é identificado por um nome e recebe configurações como image, build, ports, environment, volumes, depends_on, entre outras. É importante conhecer as principais diretivas para criar arquivos eficientes.
Vejamos um exemplo simples de docker-compose.yml para uma aplicação web com banco de dados:
version: "3.9"
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- api
api:
build: ./api
environment:
- DB_HOST=db
- DB_USER=user
- DB_PASSWORD=pass
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:Neste arquivo, definimos três serviços: web (Nginx), api (construído a partir de um Dockerfile em ./api) e db (PostgreSQL). O serviço web publica a porta 8080 e monta um diretório local. O api recebe variáveis de ambiente e depende do db. O volume db_data é nomeado para persistir os dados do banco.
Comandos úteis: docker-compose up -d para iniciar em segundo plano, docker-compose down para parar e remover, docker-compose ps para listar serviços, docker-compose logs para ver logs.
Serviços e dependências
Cada serviço no Compose representa um container. A diretiva depends_on controla a ordem de inicialização e encerramento. Se um serviço depende de outro, o Compose inicia o dependido primeiro e para o dependente primeiro no shutdown. No entanto, depends_on não espera o serviço estar pronto para receber conexões; ele apenas espera o container iniciar. Para garantir que um serviço esteja saudável, é comum usar health checks ou scripts de espera.
Por exemplo, um backend que depende do banco de dados pode precisar aguardar o PostgreSQL aceitar conexões. Podemos usar a opção condition: service_healthy se definirmos um healthcheck no serviço do banco. Isso garante que o backend só inicie quando o banco estiver saudável.
services:
db:
image: postgres:15
healthcheck:
test: ["CMD", "pg_isready", "-U", "user"]
interval: 5s
timeout: 5s
retries: 5
api:
build: .
depends_on:
db:
condition: service_healthyAlém de depends_on, você pode usar a diretiva links (legado), mas é recomendado usar redes personalizadas. Por padrão, o Compose cria uma rede para o projeto, e os serviços se comunicam pelo nome do serviço. Você também pode definir redes customizadas para isolar grupos de serviços.
Outros aspectos importantes: environment define variáveis de ambiente, env_file carrega de um arquivo, volumes para persistência e compartilhamento, networks para conectar a redes específicas, ports para expor portas, e build para construir a imagem a partir de um Dockerfile.
Casos de uso
O Docker Compose é amplamente utilizado em vários cenários. Um dos mais comuns é o desenvolvimento local: com um único arquivo, você sobe toda a stack necessária (banco, cache, fila, etc.) e replica o ambiente de produção. Isso garante consistência entre máquinas de desenvolvedores.
Outro caso é a integração contínua: pipelines podem usar Docker Compose para subir serviços de teste e executar a suíte de testes. Ferramentas como GitHub Actions, GitLab CI e Jenkins podem invocar docker-compose up para preparar o ambiente.
Também é útil para demonstrações e prototipagem rápida: você pode criar um ambiente completo com poucas linhas e compartilhar com colegas. Em produção, embora não seja recomendado para grandes orquestrações, pode ser usado em servidores únicos ou pequenos deploys.
Exemplo de um caso de uso com múltiplos serviços: uma aplicação que consiste em um frontend (React), um backend (Node.js), um banco (PostgreSQL), um cache (Redis) e um worker (Python). O Compose permite subir tudo com um comando e escalar serviços individuais com docker-compose up --scale worker=3.
services:
frontend:
build: ./frontend
ports: ["3000:3000"]
backend:
build: ./backend
ports: ["5000:5000"]
environment:
- DB_HOST=db
- REDIS_HOST=redis
db:
image: postgres:15
redis:
image: redis:7
worker:
build: ./worker
depends_on:
- db
- redisBoas práticas
Ao trabalhar com Docker Compose, algumas práticas melhoram a robustez e a segurança. Use volumes nomeados para dados persistentes, evite montar o diretório inteiro do projeto em produção, e prefira imagens oficiais quando possível. Defina health checks para dependências críticas e use depends_on com condição para garantir a prontidão.
Separe configurações por ambiente usando múltiplos arquivos: docker-compose.yml como base, docker-compose.override.yml para desenvolvimento (já usado por padrão) e docker-compose.prod.yml para produção. Utilize variáveis de ambiente com ${VAR} e um arquivo .env para evitar segredos no código.
Mantenha os serviços com responsabilidade única e evite colocar múltiplos processos em um container. Documente os comandos principais no README do projeto. E lembre-se de versionar o arquivo docker-compose.yml para que toda a equipe use a mesma configuração.
Referências
- Documentação oficial do Docker Compose
- Referência do arquivo Compose
- Casos de uso do Docker Compose
- Controle de ordem de inicialização
- Variáveis de ambiente no Compose
- Referência do Dockerfile
Exercícios
- Exercício 1: Crie um arquivo
docker-compose.ymlpara uma aplicação que tenha um serviçowebusando a imagemnginx:alpinee um serviçodbusandopostgres:15. O serviço web deve publicar a porta 8080 para a porta 80 do container. O banco deve ter um volume nomeadopgdatapara persistência.
version: "3.9"
services:
web:
image: nginx:alpine
ports:
- "8080:80"
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:- Exercício 2: No arquivo do exercício anterior, adicione um serviço
backendque construa a imagem a partir do diretório./backende que defina variáveis de ambiente para conectar ao banco (host, usuário, senha). Usedepends_onpara garantir que o banco inicie antes.
version: "3.9"
services:
web:
image: nginx:alpine
ports:
- "8080:80"
backend:
build: ./backend
environment:
- DB_HOST=db
- DB_USER=user
- DB_PASSWORD=pass
depends_on:
- db
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:- Exercício 3: Explique a diferença entre
depends_onsimples e usar health check comcondition: service_healthy. Dê um exemplo de quando é necessário.
depends_on simples apenas aguarda o container ser iniciado, não que o serviço esteja pronto para aceitar conexões. Com health check, o Compose aguarda o serviço estar saudável, garantindo que dependências como banco de dados estejam prontas. Isso é necessário quando o serviço dependente tenta se conectar imediatamente e falha se o banco ainda não estiver pronto.- Exercício 4: Escreva um comando para escalar um serviço chamado
workerpara 3 instâncias usando Docker Compose.
docker-compose up --scale worker=3- Exercício 5: Crie um
docker-compose.ymlpara uma aplicação que use Redis como cache e um serviçoappque depende do Redis. Adicione um health check no Redis e usecondition: service_healthynodepends_ondo serviçoapp.
version: "3.9"
services:
redis:
image: redis:7
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
app:
build: ./app
depends_on:
redis:
condition: service_healthy