Três pilares do Docker Compose em produção: healthcheck, política de reinicialização e limites de recursos

O alerta do servidor chegou por SMS e me arrancou da cama.
Abri o computador e vi que o contêiner aparecia como running, com aquele pequeno indicador verde que parecia perfeitamente saudável. Mas, ao acessar o serviço, recebi um 502 Bad Gateway.
O contêiner do banco de dados ainda não havia terminado de iniciar, mas o da aplicação já tentava se conectar. A conexão falhou e o serviço caiu. O contêiner continuava “em execução”, embora o serviço já estivesse morto.
Foi isso que aconteceu na primeira vez em que implantei o Docker Compose em produção.
Conseguir executar e executar com estabilidade são coisas completamente diferentes. O fato de docker-compose up iniciar tudo com um único comando não significa que a aplicação continuará de pé quando houver um estouro de memória ou uma falha de processo no meio da madrugada.
Os três pilares do Docker Compose em produção — healthcheck, política de reinicialização e limites de recursos — existem justamente para resolver esse problema. Neste artigo, compartilho as configurações que consolidei depois de enfrentar essas situações, além de um modelo YAML pronto para copiar. A ideia é transformar seus contêineres de algo que apenas “funciona” em algo que “funciona com estabilidade”.
1. Healthcheck: verifique se o contêiner está realmente disponível
O status running mostrado por docker ps informa apenas que o processo do contêiner continua em execução. Ele consegue atender às requisições normalmente? Não dá para saber só por esse status.
O healthcheck permite que o Docker faça uma “verificação periódica” do contêiner: envia uma requisição HTTP, tenta se conectar ao banco de dados ou executa um script para descobrir se o serviço está realmente ativo.
Como o healthcheck funciona?
No intervalo definido por você, o Docker envia um comando de verificação para o contêiner. Se o comando retornar 0, ele está saudável; se retornar um valor diferente de 0, não está. Depois de várias falhas consecutivas, o contêiner recebe o status unhealthy.
O ponto principal é: uma falha no healthcheck não dispara uma reinicialização automática. Ela apenas expõe o estado e avisa que “há algo errado com este serviço”. Para recuperar o serviço automaticamente, você precisa combinar o healthcheck com a inicialização condicional de depends_on e uma política de reinicialização.
Quatro parâmetros que você precisa entender
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 30s # Intervalo entre as verificações
timeout: 10s # Tempo limite de cada verificação
retries: 3 # Falhas consecutivas antes de marcar como unhealthy
start_period: 60s # Período de tolerância; falhas aqui não contam em retries
Já ignorei o parâmetro start_period e o resultado foi este: a aplicação demorava para iniciar e levava 40 segundos para se conectar ao banco de dados, mas o healthcheck começava aos 10 segundos. Depois de três falhas consecutivas, o contêiner era marcado imediatamente como unhealthy. Com start_period: 60s, a aplicação passou a ter tempo suficiente para concluir a inicialização.
Comandos comuns de healthcheck
Serviço Web:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
PostgreSQL:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
Redis:
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
Combine com depends_on para controlar a ordem de inicialização
Essa é a aplicação mais útil do healthcheck: fazer um serviço esperar até que suas dependências estejam realmente prontas antes de iniciar.
services:
app:
depends_on:
db:
condition: service_healthy # Aguarda o healthcheck do banco de dados
redis:
condition: service_healthy # Aguarda o healthcheck do Redis
Eu usava depends_on: [db, redis]. O contêiner da aplicação iniciava enquanto o banco de dados ainda estava sendo preparado, não conseguia se conectar e encerrava com erro. Depois de trocar para condition: service_healthy, a aplicação passou a esperar tranquilamente até que o banco de dados respondesse ao pg_isready. Problema resolvido.
2. Política de reinicialização (restart): dê autorrecuperação ao contêiner
Quando o contêiner cai, quem o coloca de pé novamente?
Executar docker restart manualmente? Tente fazer isso quando o alerta chegar às três da manhã.
A política de reinicialização delega essa tarefa ao daemon do Docker. Depois que o contêiner termina, o Docker decide automaticamente se deve reiniciá-lo.
Comparação entre as quatro políticas
| Política | Comportamento | Quando usar |
|---|---|---|
no | Encerra e não reinicia | Testes temporários, CI/CD |
always | Reinicia independentemente da forma de encerramento | Serviços essenciais |
on-failure | Reinicia apenas após uma saída com erro | Contêineres de tarefas |
unless-stopped | Sempre reinicia, exceto após uma parada manual | Preferida em produção |
A melhor opção para produção: unless-stopped
restart: unless-stopped
Por que recomendar unless-stopped em vez de always?
A diferença está no comportamento depois de executar docker stop manualmente.
always: depois de uma parada manual, o contêiner volta a iniciar se o sistema ou o serviço do Docker for reiniciadounless-stopped: depois de uma parada manual, o contêiner permanece realmente parado e não volta sozinho
Imagine parar um contêiner manualmente para fazer manutenção e, depois de reiniciar o servidor, descobrir que ele voltou a executar por conta própria. A reação natural seria perguntar: o que aconteceu aqui?
Limite de tentativas com on-failure
É possível definir o número de tentativas de on-failure:
restart: on-failure:5 # No máximo 5 reinicializações
Se o contêiner falhar ao iniciar cinco vezes seguidas, o Docker desiste. Essa configuração é adequada para situações em que fatores externos, como um banco de dados inacessível ou uma configuração incorreta, podem causar falhas repetidas. Assim, você evita um ciclo infinito de reinicializações.
Como escolher? Em resumo
- Serviços essenciais (Web, API e banco de dados):
unless-stopped - Tarefas em segundo plano e scripts agendados:
on-failure - Desenvolvimento, depuração e execuções temporárias:
no
Uma armadilha: a política de reinicialização só determina “se o contêiner deve reiniciar depois de encerrar”. Ela não verifica “se o serviço está realmente disponível”. Para cobrir os dois cenários, é preciso combiná-la com um healthcheck.
3. Limites de recursos (deploy.resources): evite que o contêiner saia de controle
Você já viu um contêiner com vazamento de memória consumir toda a RAM do servidor e fazer com que os demais fossem encerrados pelo OOM Killer?
Eu já. Não é uma experiência agradável.
Os limites de recursos definem um “teto” para cada contêiner: se ele ultrapassar esse limite, será encerrado, protegendo os outros serviços.
limits vs. reservations
deploy:
resources:
limits:
cpus: '1.0' # No máximo 1 CPU
memory: 512M # No máximo 512 MB de memória
reservations:
cpus: '0.5' # Reserva mínima de 0,5 CPU
memory: 256M # Reserva mínima de 256 MB de memória
- limits: limite rígido; se for ultrapassado, o processo é encerrado (OOM)
- reservations: limite flexível; informa ao agendador que “este contêiner precisa de pelo menos esta quantidade de recursos”
Em termos simples, limits significa “não pode ultrapassar”, enquanto reservations significa “garanta pelo menos isto”.
Como definir o limite de CPU?
cpus: '1.0' # Pode usar no máximo 1 núcleo de CPU por completo
cpus: '0.5' # Pode usar no máximo 50% de uma CPU
cpus: '2.0' # Pode usar no máximo 2 núcleos
O limite de CPU é flexível: ao ultrapassá-lo, o contêiner é desacelerado, não encerrado. Por isso, é melhor deixar alguma margem.
Como definir o limite de memória?
memory: 512M # 512 MB
memory: 2G # 2 GB
O limite de memória é rígido. Se for ultrapassado, o contêiner é encerrado pelo OOM Killer, sem negociação.
Valores que costumo usar:
- Aplicações Node.js: pelo menos
512M; em produção, recomendo1G - Aplicações Python:
256M - 512M - PostgreSQL: entre
1G - 4G, conforme o número de conexões e o volume de dados - Redis:
256M - 512M; um cache maior pode exigir mais memória
Uma configuração prática
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
Com essa configuração, a aplicação pode usar no máximo uma CPU e 1 GB de memória, enquanto o Docker garante pelo menos 0,25 CPU e 256 MB de memória.
Observação: a configuração deploy é voltada principalmente ao Docker Swarm. Em uma implantação de host único com docker-compose up, os limites de recursos funcionam com o Docker Compose V2 ou com o parâmetro docker-compose --compatibility. Uma opção mais comum em um único host é usar mem_limit e cpus — hoje obsoletos — ou adotar diretamente deploy, compatível com o Compose V2.20+.
4. Modelo completo: YAML pronto para produção e para copiar
Cada um dos três pilares já é útil por si só, mas é a combinação deles que forma uma configuração pronta para produção. A seguir, há um exemplo completo com um serviço Web, PostgreSQL e Redis, pronto para copiar e adaptar.
Exemplo completo
version: '3.8'
services:
# Aplicação Web
app:
image: myapp:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
logging:
driver: json-file
options:
max-size: "10m" # Máximo de 10 MB por arquivo de log
max-file: "3" # Mantém no máximo 3 arquivos de log
# Banco de dados PostgreSQL
db:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppassword
POSTGRES_DB: appdb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
volumes:
- pgdata:/var/lib/postgresql/data
# Cache Redis
redis:
image: redis:7-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 128M
volumes:
pgdata:
Como separar as configurações
As configurações dos ambientes de desenvolvimento e produção costumam ser diferentes. Gerencie-as em dois arquivos separados:
# compose.yaml - Ambiente de desenvolvimento
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
restart: "no" # Não queremos reinicialização automática durante o desenvolvimento
# compose.production.yaml - Substituições para produção
version: '3.8'
services:
app:
image: myapp:v1.2.3 # Imagem já criada para produção
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 1G
Inicie o ambiente de produção:
docker-compose -f compose.yaml -f compose.production.yaml up -d
Os dois arquivos são combinados, e as configurações de compose.production.yaml sobrescrevem as de compose.yaml.
Gerenciamento de logs: evite ocupar todo o disco
O driver de logs padrão do Docker é json-file, e esses logs crescem indefinidamente. Sem limites, eles podem ocupar todo o disco depois de alguns meses.
logging:
driver: json-file
options:
max-size: "10m" # Máximo de 10 MB por arquivo
max-file: "3" # No máximo 3 arquivos, totalizando até 30 MB
Cada contêiner mantém no máximo 30 MB de logs, com rotação automática. Adiciono essa configuração a todos os serviços para não precisar fazer uma limpeza manual no futuro.
Conclusão
Depois de tudo isso, a lógica central dos três pilares é:
O healthcheck detecta o problema → a política de reinicialização recupera o serviço → os limites de recursos impedem que a falha se espalhe
Com essa combinação, sua aplicação no Docker Compose consegue:
- Esperar que as dependências estejam realmente prontas antes de iniciar, em vez de tentar avançar às cegas
- Recuperar-se sozinha após uma falha, sem tirar você da cama no meio da madrugada
- Impedir que um contêiner fora de controle derrube o servidor inteiro
Agora, confira seu docker-compose.yml. Qual dessas configurações está faltando? Adicione-a.
Se você ainda não usa o Docker Compose para implantar em produção, leve essas três configurações para a próxima implantação. Seu eu do futuro vai agradecer.
Configurar os três pilares de uma implantação do Docker Compose em produção
Adicione healthchecks, políticas de reinicialização e limites de recursos ao Docker Compose para criar uma implantação estável e pronta para produção
⏱️ Estimated time: 15 min
- 1
Step 1: Adicionar a configuração de healthcheck
Adicione uma configuração healthcheck a cada serviço:
• test: comando de verificação (curl, pg_isready, redis-cli ping etc.)
• interval: intervalo entre verificações (recomendado: 10 a 30s)
• timeout: tempo limite (recomendado: 5 a 10s)
• retries: número de falhas (recomendado: 3 a 5)
• start_period: período de tolerância na inicialização (defina entre 30 e 60s, conforme o tempo de inicialização da aplicação) - 2
Step 2: Configurar a inicialização condicional dos serviços dependentes
Use o parâmetro condition de depends_on:
• Troque depends_on: [db] por depends_on: db: condition: service_healthy
• Verifique se o healthcheck do serviço dependente está configurado
• O serviço da aplicação aguardará até que a dependência esteja realmente disponível - 3
Step 3: Definir a política de reinicialização
Escolha a política de reinicialização conforme o tipo de serviço:
• Serviços essenciais (Web/API/banco de dados): restart: unless-stopped
• Tarefas em segundo plano/scripts agendados: restart: on-failure:5
• Desenvolvimento e depuração: restart: 'no'
• Evite always (o contêiner pode reiniciar inesperadamente após uma parada manual) - 4
Step 4: Configurar limites de recursos
Defina limits e reservations em deploy.resources:
• limits: limite rígido; ao ultrapassá-lo, o processo é encerrado pelo OOM Killer
• reservations: limite flexível; quantidade mínima de recursos garantida pelo Docker
• Aplicações Node.js: recomenda-se pelo menos 512M em limits.memory
• Bancos de dados: defina entre 1G e 4G de acordo com o número de conexões e o volume de dados - 5
Step 5: Adicionar rotação de logs
Evite que os arquivos de log ocupem todo o disco:
• logging.driver: json-file (driver padrão)
• logging.options.max-size: '10m' (máximo de 10 MB por arquivo)
• logging.options.max-file: '3' (mantém três arquivos)
• Limite total de 30 MB de logs, com rotação automática
FAQ
O contêiner reinicia automaticamente quando o healthcheck falha?
Qual é a diferença entre unless-stopped e always?
Qual é a diferença entre limits e reservations nos limites de recursos?
Para que serve o parâmetro start_period?
Como usar configurações diferentes nos ambientes de desenvolvimento e produção?
• compose.yaml: configuração do ambiente de desenvolvimento (build: ., restart: 'no')
• compose.production.yaml: substituições para produção (image: xxx, restart: unless-stopped)
• Comando de inicialização: docker-compose -f compose.yaml -f compose.production.yaml up -d
• O segundo arquivo sobrescreve as configurações do primeiro, mantendo os ambientes separados
Posso usar deploy.resources em uma implantação de host único?
10 min de leitura · Publicado em: 24 abr 2026 · Atualizado em: 4 set 2026
Guia prático Docker
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Docker Compose em produção: health checks, políticas de reinicialização e gerenciamento de logs
Guia prático de Docker Compose em produção: configuração de health checks, políticas de reinicialização e uma estratégia completa de gerenciamento de logs para recuperar contêineres automaticamente e evitar que os logs ocupem todo o disco.
Parte 11 de 34
Próximo
Docker Compose para implantar um ambiente PHP com um comando: guia completo de DNMP (Nginx + MySQL + PHP)
Aprenda passo a passo a implantar em 10 minutos um ambiente de desenvolvimento DNMP (Docker + Nginx + MySQL + PHP) com Docker Compose e elimine as diferenças de ambiente entre membros da equipe, com configurações completas e soluções para problemas comuns.
Parte 13 de 34



Comentários
Entre com GitHub para comentar