1. Retomando: do docker run repetido a um arquivo único
No artigo anterior, subir uma API e um Postgres conectados exigiu dois comandos docker run longos, com flags de rede, volume e variáveis de ambiente para lembrar (e digitar) toda vez. Em um projeto real, com mais serviços — cache, fila, worker em background — isso rapidamente vira inviável de manter na cabeça ou em um script solto. O Docker Compose resolve isso descrevendo toda a aplicação multi-container em um único arquivo declarativo, versionado junto com o código.
2. O arquivo compose.yaml
Compose lê um arquivo YAML (por convenção compose.yaml, ou o nome legado docker-compose.yml, ainda amplamente usado) descrevendo serviços (cada um vira um ou mais containers), redes e volumes:
# compose.yaml
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://postgres:segredo@banco:5432/postgres
depends_on:
- banco
banco:
image: postgres:16
environment:
POSTGRES_PASSWORD: segredo
volumes:
- pg-dados:/var/lib/postgresql/data
volumes:
pg-dados:
Isso substitui inteiramente os dois docker run do artigo anterior. Uma diferença importante já aparece aqui: por padrão, Compose cria uma rede própria para o projeto e conecta todos os serviços a ela automaticamente — não é preciso um docker network create manual, nem declarar --network em cada serviço. Cada serviço já é acessível pelos demais pelo nome declarado em services: (aqui, banco resolve para o container do Postgres), exatamente como as redes definidas pelo usuário do artigo anterior.
3. Comandos essenciais do Compose
docker compose up -d # sobe todos os serviços em segundo plano
docker compose ps # lista os containers do projeto e seu status
docker compose logs -f api # segue os logs de um serviço específico
docker compose logs -f # segue os logs de todos os serviços, intercalados
docker compose exec api bash # abre um shell dentro do container de um serviço
docker compose stop # para os containers sem removê-los
docker compose down # para e remove containers e rede (volumes nomeados sobrevivem)
docker compose down -v # remove também os volumes — cuidado, apaga dados
O padrão docker compose <comando> <serviço> se repete: a maioria dos comandos aceita opcionalmente o nome de um serviço específico (como em logs -f api) ou afeta o projeto inteiro se omitido.
4. depends_on: ordem de início, não prontidão
depends_on controla a ordem em que os containers são criados e iniciados — no exemplo acima, banco inicia antes de api. O que ele não garante é que o Postgres já esteja pronto para aceitar conexões nesse momento: o processo do Postgres pode levar alguns segundos para inicializar depois que o container começa a rodar, e uma API que tenta conectar imediatamente pode falhar nessa janela.
A forma correta de esperar prontidão de verdade é um healthcheck:
services:
api:
build: .
depends_on:
banco:
condition: service_healthy
banco:
image: postgres:16
environment:
POSTGRES_PASSWORD: segredo
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
Com condition: service_healthy, o Compose só considera banco "pronto" (e só então inicia api) depois que o healthcheck passar — muito mais confiável do que assumir um tempo fixo de espera ou implementar retry manual na aplicação (embora ter retry na aplicação continue sendo uma boa prática defensiva, mesmo com healthcheck).
5. Variáveis de ambiente e o arquivo .env
Segredos e configuração que variam por ambiente (senha de banco, chaves de API) não deveriam ficar hardcoded no compose.yaml. Compose lê automaticamente um arquivo .env no mesmo diretório e substitui variáveis referenciadas com ${NOME}:
# .env
POSTGRES_PASSWORD=segredo-local
API_PORT=8000
services:
api:
build: .
ports:
- "${API_PORT}:8000"
banco:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
Isso permite versionar o compose.yaml normalmente enquanto o .env (que contém segredos reais) fica de fora do controle de versão via .gitignore — o mesmo princípio de nunca commitar credenciais, aplicado à configuração do Compose.
6. Um ambiente completo: app + banco + cache
Estendendo o exemplo para um cenário mais realista, com Redis como cache além da API e do Postgres:
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@banco:5432/postgres
REDIS_URL: redis://cache:6379
depends_on:
banco:
condition: service_healthy
cache:
condition: service_started
banco:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pg-dados:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- redis-dados:/data
volumes:
pg-dados:
redis-dados:
Um docker compose up -d sobe os três serviços, na ordem certa, já conectados entre si e com dados persistentes — o equivalente a uma infraestrutura local completa de desenvolvimento, reproduzível em qualquer máquina que tenha Docker instalado, sem instalar Postgres ou Redis diretamente no sistema.
7. Escalando um serviço
Para rodar múltiplas instâncias do mesmo serviço (por exemplo, vários workers processando uma fila em paralelo):
docker compose up -d --scale worker=3
Isso só funciona sem conflito para serviços que não publicam uma porta fixa do host (ports:) — três containers não podem, todos, mapear a porta 8000 do host simultaneamente. Serviços pensados para escalar dessa forma tipicamente não expõem porta ao host diretamente, sendo acessados por outro serviço (como um proxy reverso) através da rede interna do Compose.
8. Conclusão e próximos passos
Docker Compose transforma uma coleção de comandos docker run frágeis e difíceis de lembrar em um arquivo único, declarativo e versionável, que descreve toda a aplicação — serviços, rede, volumes, ordem de inicialização e variáveis de ambiente. No último artigo desta série, o assunto vai para práticas de nível de produção: multi-stage builds para reduzir drasticamente o tamanho final da imagem, segurança (rodar sem root, scan de vulnerabilidades) e como tudo isso se encaixa em um pipeline de CI/CD.
Imagem de capa: Logo oficial do Docker — Wikimedia Commons, fonte: docker.com/company/newsroom/media-resources
Referências:
Top comments (0)