Docker Compose
Esta aula explora o Docker Compose, uma ferramenta essencial para orquestrar múltiplos containers. Você aprenderá a definir serviços, redes e volumes em um arquivo YAML, gerenciar dependências entre serviços e aplicar boas práticas em casos de uso reais, como ambientes de desenvolvimento e pilhas de aplicações.
O Docker Compose é uma ferramenta que permite definir e gerenciar aplicações multi-container de forma declarativa. Em vez de executar vários comandos docker run manualmente, você descreve toda a arquitetura em um arquivo YAML chamado docker-compose.yml e controla o ciclo de vida com comandos simples como docker compose up e docker compose down. Isso simplifica drasticamente o desenvolvimento, testes e implantação de sistemas que dependem de múltiplos serviços, como um backend, um banco de dados e um cache.
Nesta aula, vamos mergulhar nos conceitos fundamentais do Compose, desde a estrutura do arquivo YAML até a gestão de dependências entre serviços. Você verá como construir uma aplicação completa com banco de dados e API, entenderá os casos de uso mais comuns e aprenderá boas práticas para escrever arquivos Compose eficientes e portáveis.
Multi-container
Em aplicações modernas, é raro ter apenas um container. Por exemplo, uma aplicação web pode precisar de um servidor web, um backend, um banco de dados e um serviço de cache. Cada um desses componentes roda em seu próprio container, com sua própria imagem e configuração. Gerenciar esses containers individualmente é trabalhoso e propenso a erros, especialmente quando há dependências entre eles.
O Docker Compose resolve esse problema permitindo que você defina todos os serviços em um único arquivo. Com um comando, você pode criar e iniciar todos os containers, configurar redes entre eles e montar volumes compartilhados. Além disso, o Compose cuida da ordem de inicialização quando você define dependências, garantindo que serviços essenciais, como o banco de dados, estejam prontos antes que o backend comece.
Vamos ilustrar com um exemplo prático. Suponha que você tenha uma aplicação Node.js que usa um banco de dados PostgreSQL. Sem o Compose, você precisaria executar:
docker network create app-network
docker run -d --name db --network app-network -e POSTGRES_PASSWORD=secret postgres
docker run -d --name app --network app-network -p 3000:3000 myappCom o Compose, você define tudo em um arquivo e executa apenas docker compose up -d. Isso não só economiza tempo, mas também torna a configuração versionável e reproduzível.
docker-compose.yml
O arquivo docker-compose.yml é o coração do Compose. Ele segue a especificação YAML e contém a definição de serviços, redes e volumes. A estrutura básica é:
version: "3.8"
services:
web:
image: nginx:latest
ports:
- "8080:80"
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: exampleNo exemplo acima, definimos dois serviços: web e db. Cada serviço especifica uma imagem e outras configurações, como portas e variáveis de ambiente. A chave version é opcional, mas recomendada para compatibilidade; as versões mais recentes do Compose (2.x) não exigem essa chave, mas é comum incluí-la para documentar a especificação.
Outras configurações comuns incluem:
build: para construir a imagem a partir de um Dockerfile.volumes: para montar volumes (bind mounts ou volumes nomeados).networks: para conectar o serviço a redes específicas.depends_on: para definir dependências entre serviços.environment: para variáveis de ambiente.ports: para mapear portas do host para o container.restart: para definir políticas de reinicialização.
Vejamos um exemplo mais completo com build e volumes:
version: "3.8"
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
environment:
NODE_ENV: development
db:
image: mongo:5
volumes:
- db-data:/data/db
volumes:
db-data:Aqui, o serviço web é construído a partir do Dockerfile no diretório atual, e o código local é montado em /app para desenvolvimento. O banco de dados usa um volume nomeado db-data para persistir os dados.
Serviços e dependências
Os serviços são a unidade central do Compose. Cada serviço representa um container que será criado a partir de uma imagem ou de um build. Os serviços podem se comunicar entre si através da rede criada automaticamente pelo Compose, usando o nome do serviço como hostname. Por exemplo, se você tem um serviço api e um db, o código da API pode se conectar ao banco usando o hostname db e a porta interna do container.
As dependências entre serviços são definidas com depends_on. Isso garante que o Compose inicie os serviços na ordem correta. Por exemplo:
services:
api:
build: ./api
depends_on:
- db
db:
image: postgres:13Nesse caso, o serviço db será iniciado antes de api. No entanto, depends_on não espera que o banco esteja pronto para aceitar conexões; ele apenas espera que o container seja iniciado. Para aplicações que exigem que o banco esteja acessível, é comum usar scripts de espera ou health checks. O Compose também suporta a condição service_healthy quando combinado com health checks:
services:
api:
build: ./api
depends_on:
db:
condition: service_healthy
db:
image: postgres:13
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s
timeout: 5s
retries: 5Isso garante que o serviço api só seja iniciado depois que o health check do banco passar, tornando a inicialização mais robusta.
Casos de uso
O Docker Compose é amplamente utilizado em diversos cenários. O mais comum é em ambientes de desenvolvimento, onde você precisa de uma pilha completa (banco, cache, filas) sem instalar tudo localmente. Com o Compose, você sobe toda a infraestrutura com um comando e a derruba com outro, mantendo o ambiente limpo.
Outro caso de uso é para testes automatizados. Você pode definir uma pilha de teste com serviços efêmeros e executar seus testes contra ela. O Compose também é usado em pipelines de CI/CD para criar ambientes isolados para cada execução.
Além disso, o Compose é útil para aplicações em produção de pequeno e médio porte, embora para grandes escalas seja comum usar orquestradores como Kubernetes. Mesmo assim, o Compose é uma ferramenta valiosa para prototipar e para implantações simples.
Vejamos um exemplo de caso de uso completo: uma aplicação web com Node.js, banco PostgreSQL e nginx como proxy reverso.
version: "3.8"
services:
app:
build: ./app
environment:
DB_HOST: db
DB_USER: postgres
DB_PASSWORD: secret
depends_on:
db:
condition: service_healthy
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s
timeout: 5s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
volumes:
db-data:Nesse exemplo, o nginx atua como proxy reverso para o serviço app, que se conecta ao banco. O volume db-data garante a persistência dos dados do banco.
Boas práticas
Ao trabalhar com Docker Compose, algumas boas práticas podem melhorar a eficiência e a segurança:
- Use nomes de serviços descritivos e em minúsculas.
- Prefira volumes nomeados para dados persistentes, em vez de bind mounts, a menos que seja para desenvolvimento.
- Especifique versões de imagens (tags) exatas, evitando
latestem produção. - Utilize variáveis de ambiente com arquivos
.envpara configurar diferentes ambientes. - Separe os arquivos Compose por ambiente (ex.:
docker-compose.dev.ymledocker-compose.prod.yml) ou use overrides. - Inclua health checks para serviços críticos e use
depends_oncom condições. - Mantenha os serviços o mais enxutos possível, usando imagens oficiais e otimizadas.
Referências
- Docker Compose Documentation
- Compose File Reference
- Features and Uses
- Docker Compose CLI
- Startup Order and Dependencies
Exercícios
Crie um arquivo
docker-compose.ymlque defina dois serviços: um serviçowebusando a imagemnginx:alpinee um serviçoredisusando a imagemredis:alpine. O serviçowebdeve mapear a porta 8080 do host para a porta 80 do container, e o serviçoredisdeve expor a porta 6379.✓ Resposta:version: "3.8" services: web: image: nginx:alpine ports: - "8080:80" redis: image: redis:alpine ports: - "6379:6379"No arquivo Compose do exercício anterior, adicione uma dependência para que o serviço
webdependa do serviçoredis. Explique o que isso garante.✓ Resposta: Adicionedepends_on: [redis]ao serviçoweb. Isso garante que o containerredisseja iniciado antes doweb, mas não espera que o Redis esteja pronto para conexões. Para esperar a prontidão, seria necessário usar health checks.Defina um serviço
apique seja construído a partir de um Dockerfile no diretório./api. O serviço deve montar o diretório local./apiem/app(bind mount) e definir a variável de ambienteNODE_ENV=development.✓ Resposta:version: "3.8" services: api: build: ./api volumes: - ./api:/app environment: NODE_ENV: developmentCrie um serviço
dbusando a imagempostgres:13com um volume nomeadopgdatamontado em/var/lib/postgresql/data. Defina a variável de ambientePOSTGRES_PASSWORD=secret. Declare o volume na seção de volumes do arquivo.✓ Resposta:version: "3.8" services: db: image: postgres:13 environment: POSTGRES_PASSWORD: secret volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:Escreva um comando para iniciar todos os serviços em segundo plano a partir de um arquivo Compose chamado
docker-compose.ymle um comando para parar e remover os containers, redes e volumes definidos no arquivo.✓ Resposta: Para iniciar em segundo plano:docker compose up -d. Para parar e remover tudo (incluindo volumes):docker compose down -v. O-vremove volumes nomeados; sem ele, os volumes são preservados.