Nos artigos anteriores desta série, subimos containers, criamos redes e volumes manualmente, um comando docker run de cada vez. Funciona, mas não escala: numa aplicação real com banco de dados, cache, API e worker, decorar (ou repetir) todos aqueles comandos vira um problema. O Docker Compose resolve isso descrevendo toda a stack num único arquivo declarativo.
1. De Comandos Soltos para Um Arquivo Declarativo
Tudo que vimos até aqui — --network, --volume, --publish, --env — tem um equivalente direto no Compose, só que declarado em YAML e versionado junto com o código. Em vez de lembrar (ou documentar à parte) a sequência exata de comandos para subir o ambiente, qualquer pessoa do time roda docker compose up e tem tudo funcionando.
2. Um compose.yaml Básico
Vamos recriar a stack API + Postgres do artigo anterior, agora como Compose:
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: exemplo
volumes:
- dados-postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
api:
build: .
environment:
DATABASE_HOST: postgres
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
volumes:
dados-postgres:Repare no que o Compose já resolve para você:
- Rede: por padrão, o Compose cria uma rede exclusiva para o projeto e conecta todos os serviços nela — o
apijá enxergapostgrespelo nome, sem precisar criar rede manualmente. - Volumes nomeados: declarados uma vez em
volumes:, referenciados pelo nome nos serviços. build: .diz para o Compose construir a imagem a partir doDockerfileno diretório atual, em vez de puxar de um registry.
3. depends_on com Healthcheck
Um erro comum é achar que depends_on espera o serviço “estar pronto”. Sem condition, ele só espera o container iniciar, não que a aplicação dentro dele esteja de fato aceitando conexões — o Postgres pode ainda estar inicializando quando a API já tenta se conectar.
O bloco healthcheck no serviço postgres, combinado com condition: service_healthy no depends_on da api, resolve isso: o Compose só sobe a api depois que o healthcheck do Postgres reportar sucesso.
4. Comandos do Dia a Dia
# Sobe todos os serviços em background
docker compose up --detach
# Mostra o status dos serviços
docker compose ps
# Acompanha os logs de todos os serviços (ou de um específico)
docker compose logs --follow
docker compose logs --follow api
# Reconstrói a imagem de um serviço após mudanças no Dockerfile
docker compose build api
# Para e remove containers, rede e (opcionalmente) volumes
docker compose down
docker compose down --volumesdocker compose down --volumes remove também os volumes nomeados — útil para começar do zero em desenvolvimento, perigoso se usado sem pensar num ambiente com dados reais.
5. Variando Configuração por Ambiente
Uma prática comum é manter um compose.yaml base e um compose.override.yaml (carregado automaticamente pelo Compose) só com o que muda em desenvolvimento:
# compose.override.yaml
services:
api:
volumes:
- ./src:/app/src
environment:
DEBUG: "true"Em produção, você ignora o override (docker compose --file compose.yaml up) ou usa um arquivo de produção explícito. Isso evita duplicar a definição inteira do serviço só para trocar duas ou três configurações entre ambientes.
6. Profiles: Serviços Opcionais
Nem todo serviço precisa subir sempre. profiles permite marcar serviços como opcionais, ativados só quando necessário — por exemplo, uma ferramenta de administração do banco que só faz sentido em desenvolvimento:
services:
pgadmin:
image: dpage/pgadmin4
profiles: ["debug"]
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin# Sobe só os serviços padrão (sem o pgadmin)
docker compose up --detach
# Sobe incluindo os serviços do profile "debug"
docker compose --profile debug up --detach7. Conclusão da Série
Ao longo destes quatro artigos, saímos de docker run hello-world até uma stack multi-serviço orquestrada por um único compose.yaml, passando por Dockerfiles otimizados com cache e multi-stage builds, e por redes e volumes explicados de forma que fazem sentido na prática. Esse é o conjunto mínimo de conhecimento que separa “sei rodar um container” de “sei containerizar uma aplicação real” — e é a base sobre a qual ferramentas de orquestração maiores, como Kubernetes, constroem seus próprios conceitos.