Executar aplicações multi-container em produção requer muito mais do que um arquivo docker-compose.yml básico. Embora o Docker Compose seja comumente associado ao desenvolvimento local, ele é uma ferramenta poderosa para fazer deploy de cargas de trabalho em produção em ambientes de host único e clusters pequenos quando configurado corretamente. Este guia leva você passo a passo pelo processo de fortalecer sua configuração Docker Compose para produção com health checks, políticas de reinício, limites de recursos, gestão de segredos, logging centralizado e proxy reverso Nginx com terminação SSL.
Pré-requisitos
- Servidor Linux com Docker Engine 24+ e Docker Compose v2 instalados
- Nome de domínio apontando para o IP público do seu servidor
- Familiaridade básica com conceitos Docker (imagens, containers, volumes, redes)
- Certificado SSL ou disposição para usar Let’s Encrypt com Certbot
- Acesso SSH ao seu servidor de produção
- Pelo menos 2 GB de RAM e 2 núcleos de CPU disponíveis para seu stack
Configuração do Docker Compose para Produção
Um docker-compose.yml pronto para produção difere significativamente de uma configuração de desenvolvimento. Você precisa de políticas de reinício explícitas, health checks, restrições de recursos e sistemas de arquivos somente leitura onde possível.
Políticas de Reinício
As políticas de reinício garantem que seus containers se recuperem automaticamente de crashes, OOM kills ou reinícios do host:
services:
web:
image: myapp:latest
restart: unless-stopped
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
Use unless-stopped para a maioria dos serviços. Isso reinicia containers após crashes e reinícios do host, mas respeita paradas manuais. Para serviços críticos que devem estar sempre em execução, use always. Evite no em produção — isso deixa containers com falha inativos até intervenção manual.
Health Checks
Os health checks permitem ao Docker monitorar se sua aplicação está realmente funcionando, não apenas se o processo está em execução:
services:
api:
image: myapp-api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
O start_period dá à sua aplicação tempo para inicializar antes que o Docker comece a contar as verificações falhadas. Configure-o mais alto que o tempo médio de inicialização da sua aplicação. Use endpoints de saúde específicos em vez de verificar se uma porta está aberta — um processo pode escutar em uma porta enquanto está em estado quebrado.
Limites de Recursos
Sem limites de recursos, um único container descontrolado pode consumir toda a memória do sistema e derrubar tudo:
services:
api:
image: myapp-api:latest
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
Configure limits para o máximo que um serviço deveria usar e reservations para garantir recursos mínimos. Monitore seus containers com docker stats por uma semana antes de definir os limites finais. Limites muito apertados causam OOM kills e desempenho degradado.
Sistemas de Arquivos Somente Leitura
Minimize a superfície de ataque executando containers com sistemas de arquivos raiz somente leitura:
services:
api:
image: myapp-api:latest
read_only: true
tmpfs:
- /tmp
- /var/run
volumes:
- app-data:/app/data
Isso impede que processos maliciosos escrevam no sistema de arquivos do container. Use tmpfs para diretórios que precisam de acesso temporário de escrita e volumes nomeados para dados persistentes.
Gestão de Segredos e Variáveis de Ambiente
Credenciais codificadas diretamente no seu arquivo compose são um risco de segurança. O Docker Compose suporta múltiplas abordagens para gestão de segredos.
Usando Arquivos .env
Crie um arquivo .env junto ao seu arquivo compose:
# .env - NUNCA faça commit deste arquivo
POSTGRES_PASSWORD=sua_senha_segura_aqui
API_SECRET_KEY=outro_valor_seguro
REDIS_PASSWORD=senha_redis_aqui
Referencie-os no seu arquivo compose:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
Adicione .env ao seu .gitignore imediatamente. Para pipelines CI/CD, injete variáveis de ambiente do seu gerenciador de segredos (GitHub Secrets, AWS Secrets Manager ou HashiCorp Vault).
Docker Secrets
Para um manuseio mais seguro, use Docker secrets que são montados como arquivos em vez de variáveis de ambiente:
secrets:
db_password:
file: ./secrets/db_password.txt
services:
db:
image: postgres:16
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
Muitas imagens oficiais do Docker suportam a convenção do sufixo _FILE, lendo o segredo de um arquivo montado em vez de uma variável de ambiente. Isso mantém as credenciais fora da saída do docker inspect e das listagens de variáveis de ambiente do processo.
Proxy Reverso e SSL com Nginx
Expor portas de aplicação diretamente é inseguro e inflexível. Use Nginx como proxy reverso com terminação SSL:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot-webroot:/var/www/certbot:ro
- certbot-certs:/etc/letsencrypt:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
certbot:
image: certbot/certbot
volumes:
- certbot-webroot:/var/www/certbot
- certbot-certs:/etc/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done'"
volumes:
certbot-webroot:
certbot-certs:
A configuração do Nginx deve fazer proxy para seus serviços internos:
upstream api_backend {
server api:8080;
}
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://api_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Observe que apenas o serviço Nginx expõe portas ao host. Todos os outros serviços se comunicam através da rede interna do Docker, reduzindo significativamente a superfície de ataque.
Logging e Monitoramento
Deploys em produção precisam de logging estruturado com rotação para evitar esgotamento de disco.
Logging JSON File com Rotação
services:
api:
image: myapp-api:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
tag: "{{.Name}}"
Sem max-size e max-file, os logs do Docker crescem indefinidamente e podem encher seu disco. O driver json-file é o padrão e funciona bem para a maioria das configurações. Para ambientes multi-host, considere encaminhar logs para um sistema centralizado.
Encaminhamento para Sistemas Externos
Para observabilidade em produção, encaminhe logs para um serviço de agregação:
services:
api:
image: myapp-api:latest
logging:
driver: syslog
options:
syslog-address: "tcp://logserver:514"
tag: "myapp-api"
As alternativas incluem os drivers fluentd e gelf para stacks ELK ou Graylog. Qualquer que seja sua escolha, sempre configure a rotação de logs no nível do daemon do Docker como rede de segurança em /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Monitoramento Básico
Adicione monitoramento de containers com um stack leve:
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
deploy:
resources:
limits:
memory: 256M
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
deploy:
resources:
limits:
memory: 128M
Comparação: Docker Compose vs Kubernetes vs Docker Swarm
| Recurso | Docker Compose | Kubernetes | Docker Swarm |
|---|---|---|---|
| Complexidade de configuração | Baixa | Alta | Média |
| Suporte multi-host | Não (host único) | Sim (cluster) | Sim (cluster) |
| Auto-scaling | Não | Sim (HPA) | Limitado |
| Descoberta de serviços | Baseado em DNS | DNS + Ingress | Baseado em DNS |
| Gestão de segredos | Baseada em arquivos | Integrada (etcd) | Integrada |
| Atualizações progressivas | Manual | Integrada | Integrada |
| Health checks | Sim | Sim (liveness/readiness) | Sim |
| Limites de recursos | Sim | Sim (requests/limits) | Sim |
| Curva de aprendizado | Suave | Íngreme | Moderada |
| Ideal para | Host único, equipes pequenas | Larga escala, multi-equipe | Clusters pequenos |
Docker Compose é a escolha certa quando você faz deploy em um único servidor ou um número pequeno de servidores, sua equipe é pequena e você não precisa de auto-scaling. Muitos produtos SaaS bem-sucedidos rodam inteiramente em Docker Compose em produção.
Cenário Real
Você está fazendo deploy de uma aplicação web composta por uma API Node.js, um banco de dados PostgreSQL, um cache Redis e um proxy reverso Nginx. O servidor tem 4 GB de RAM e 2 núcleos de CPU.
Aqui está o arquivo compose completo de produção:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot-certs:/etc/letsencrypt:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
deploy:
resources:
limits:
cpus: "0.25"
memory: 64M
logging:
driver: json-file
options:
max-size: "5m"
max-file: "3"
api:
image: myapp-api:1.2.3
env_file: .env
secrets:
- db_password
- api_key
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
read_only: true
tmpfs:
- /tmp
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_DB: myapp
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
cpus: "0.5"
memory: 1G
reservations:
memory: 256M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 3
deploy:
resources:
limits:
cpus: "0.25"
memory: 256M
logging:
driver: json-file
options:
max-size: "5m"
max-file: "3"
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
volumes:
pgdata:
redisdata:
certbot-certs:
Faça o deploy com:
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs --tail=50
Esta configuração aloca aproximadamente 1.8 GB dos 4 GB disponíveis, deixando margem para o sistema operacional e picos de uso.
Armadilhas e Casos Especiais
A ordem de inicialização dos containers não garante disponibilidade. Usar depends_on sozinho apenas espera o container iniciar, não que o serviço interno esteja pronto. Sempre combine depends_on com condition: service_healthy e health checks adequados.
Containers órfãos se acumulam. Quando você remove um serviço do arquivo compose, o container antigo continua em execução. Sempre execute docker compose up -d --remove-orphans para limpar containers obsoletos.
Permissões de volumes causam falhas silenciosas. Se o seu container roda como usuário não-root, o volume montado pode não ser gravável. Pré-crie volumes com a propriedade correta ou use um padrão de container init.
Docker Compose não baixa novas imagens ao reconstruir por padrão. Executar docker compose up -d reutiliza imagens em cache. Use docker compose pull && docker compose up -d para garantir que você faz deploy da versão mais recente.
A expansão de variáveis do arquivo .env pode gerar conflitos. Docker Compose interpola ${VAR} no arquivo compose antes de passá-lo ao container. Se a configuração da sua aplicação usa caracteres $, escape-os com $$ no arquivo compose.
A rotação de logs deve ser configurada por serviço e globalmente. As opções de log no nível do serviço não sobrescrevem os padrões do daemon para outros containers. Configure ambos para cobertura completa.
Redes bridge têm problemas de resolução DNS com underscores. Nomes de serviço com underscores podem não resolver corretamente em versões antigas do Docker. Use hifens nos nomes de serviço para máxima compatibilidade.
Resumo
- Use
restart: unless-stoppede health checks em cada serviço de produção - Configure limites de memória e CPU com
deploy.resourcespara evitar containers descontrolados - Gerencie segredos com Docker secrets ou arquivos
.env— nunca codifique credenciais diretamente - Coloque Nginx como proxy reverso na frente dos serviços de aplicação com terminação SSL
- Configure rotação de logs com
max-sizeemax-filepara evitar esgotamento de disco - Use
depends_oncomcondition: service_healthypara ordenamento confiável de inicialização - Execute
docker compose up -d --remove-orphanspara limpar containers obsoletos em cada deploy - Docker Compose está pronto para produção em deploys de host único e equipes pequenas