Docker Compose em produção: health checks, políticas de reinicialização e gerenciamento de logs

Os alertas do servidor não paravam de chegar ao meu celular. Abri o terminal e vi o disco com 99% de uso — os logs dos contêineres ocupavam 50 GB.
E esse nem foi o pior caso. No ano passado, em um projeto, o contêiner da API aparecia como “running”, mas a conexão com o banco de dados já tinha caído e todas as requisições retornavam 500. Foram três horas inteiras de investigação até encontrarmos o problema. Segundo dados de uma pesquisa da Last9, um contêiner que parece ativo, mas não responde, consome em média 3,2 horas de diagnóstico a cada ocorrência.
Muitas equipes, na primeira implantação do Docker Compose em produção, configuram apenas o mapeamento de portas e os volumes e colocam os contêineres no ar. Não configuram health checks nem rotação de logs e escolhem restart: always sem pensar muito. O resultado é este: o contêiner parece estar em execução, embora o serviço já tenha parado; os arquivos de log crescem sem controle até ocupar todo o disco; e um serviço com falha reinicia indefinidamente, consumindo CPU e memória.
Este artigo explica em detalhes três configurações essenciais para produção: health checks, políticas de reinicialização e gerenciamento de logs. Além de exemplos de configuração, você encontrará comandos de verificação para serviços comuns, etapas de diagnóstico e um modelo completo de docker-compose.yml pronto para copiar.
Health checks — mantenha o contêiner realmente ativo
O status running não significa que a aplicação esteja funcionando de verdade. O banco de dados pode estar inacessível, a porta pode não estar escutando ou o processo pode ter travado — e o Docker não sabe disso sozinho. O health check funciona como um monitor de batimentos do contêiner, verificando periodicamente se a aplicação ainda responde normalmente.
Sintaxe de configuração
No docker-compose.yml, a configuração de healthcheck tem este formato:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s # Verifica a cada 10 segundos
timeout: 5s # Aguarda no máximo 5 segundos por verificação
retries: 5 # Marca como unhealthy após 5 falhas consecutivas
start_period: 30s # Reserva 30 segundos para o contêiner aquecer após iniciar
Esses parâmetros precisam funcionar em conjunto. O timeout não deve ser maior que o interval; caso contrário, a próxima verificação começará antes que a anterior termine. Também não ignore o start_period: serviços como bancos de dados demoram mais para iniciar, e um período de aquecimento curto demais pode fazer o health check concluir, incorretamente, que o contêiner falhou.
Comandos de health check para serviços comuns
Cada serviço exige uma forma diferente de verificação. Veja alguns exemplos frequentes.
PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
O pg_isready é uma ferramenta incluída no PostgreSQL e serve especificamente para verificar se o banco de dados está pronto para aceitar conexões.
MySQL / MariaDB
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Observe que a senha usa $$ como escape; caso contrário, o YAML interpretará $ como uma referência de variável.
Redis
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 10s
timeout: 3s
retries: 3
O comando ping do Redis retorna PONG; o grep filtra a saída para confirmar que o resultado está correto.
Servidor web (verificação HTTP)
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
O parâmetro -f faz o curl retornar um código de saída diferente de zero quando o status HTTP não é 2xx, provocando a falha do health check.
Atenção a uma armadilha: imagens enxutas, como Alpine, podem não incluir curl. Você pode instalá-lo com apk add curl ou usar wget:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"]
Controle da ordem de inicialização
O banco de dados ainda não está pronto, mas o contêiner da API já inicia, não consegue se conectar, registra um erro e encerra — já vi isso acontecer muitas vezes. A combinação de depends_on com condition: service_healthy resolve esse problema:
services:
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
api:
build: ./api
depends_on:
postgres:
condition: service_healthy # Inicia somente após o health check do postgres ser aprovado
Com isso, o Docker Compose aguarda o health check do postgres retornar healthy antes de iniciar o contêiner api. Assim, a API não tenta mais se conectar antes que o banco de dados esteja pronto.
Política de reinicialização — recupere-se de falhas com controle
O que fazer quando um contêiner falha? Reiniciá-lo automaticamente parece uma boa ideia. O problema é que, se a causa da falha não for resolvida, a reinicialização entra em um loop infinito, desperdiça CPU e memória e ainda esconde a falha real.
Sintaxe de configuração
A política de reinicialização é configurada no bloco deploy:
deploy:
restart_policy:
condition: on-failure # Reinicia somente em caso de falha
delay: 5s # Aguarda 5 segundos antes de reiniciar
max_attempts: 3 # Tenta reiniciar no máximo 3 vezes
window: 120s # Só considera a recuperação bem-sucedida após 120 segundos
Há três opções para condition:
none: não reinicia; se o contêiner parar, continuará paradoon-failure: reinicia apenas quando o contêiner termina de forma anormal, com código de saída diferente de 0any: reinicia em qualquer situação
Recomendação para produção
Em produção, prefira on-failure a always.
Por quê? restart: always reinicia o contêiner em qualquer situação. Um bug no código derrubou a aplicação? Reinicia. O processo encerrou porque não conseguiu acessar o banco de dados? Reinicia. Um erro no arquivo de configuração impediu a inicialização? Reinicia de novo. O resultado é um loop de falhas, logs crescendo sem parar e consumo repetido de CPU.
Com on-failure e max_attempts, a situação muda: o contêiner tenta reiniciar no máximo três vezes e, se continuar falhando, para. A equipe de operações consegue perceber que ele encerrou definitivamente e investigar a causa real.
Ajuste dos parâmetros
delay é o intervalo entre reinicializações. Se for curto demais, o contêiner pode iniciar novamente antes que a limpeza anterior tenha terminado; se for longo demais, aumenta o tempo de recuperação. Em geral, de 5 a 10 segundos é uma faixa adequada.
O parâmetro window costuma ser ignorado. Ele define por quanto tempo o contêiner precisa permanecer sem falhar após a reinicialização para que ela seja considerada bem-sucedida. Por exemplo, com window: 120s, se o contêiner parar novamente dentro de 120 segundos, a contagem de max_attempts não será zerada. Isso evita considerar como sucesso um processo que reiniciou e falhou de novo um segundo depois.
Como health checks e política de reinicialização trabalham juntos
Health checks e políticas de reinicialização não funcionam de forma isolada; eles atuam em conjunto:
- O health check falha
retriesvezes seguidas → o contêiner é marcado comounhealthy - Se houver uma
restart_policyconfigurada, o Docker tentará reiniciar o contêiner - Após a reinicialização, a contagem do health check começa novamente
- Se o health check for aprovado, o contêiner volta ao normal; se continuar falhando, haverá novas tentativas até esgotar
max_attempts
Esse fluxo permite a recuperação automática de falhas e, ao mesmo tempo, limita o risco de reinicializações infinitas.
Gerenciamento de logs — evite ocupar todo o disco
O alerta das três da manhã mencionado no início — disco com 99% de uso e 50 GB de logs — já aconteceu comigo mais de uma vez. O driver de logs padrão do Docker, json-file, não remove automaticamente os logs antigos, então o arquivo cresce indefinidamente. Sem rotação de logs, mais cedo ou mais tarde o disco ficará cheio.
Configuração da rotação de logs
Adicione a configuração logging ao docker-compose.yml:
logging:
driver: "json-file"
options:
max-size: "10m" # Tamanho máximo de 10 MB por arquivo de log
max-file: "3" # Mantém no máximo 3 arquivos de log
compress: "true" # Compacta logs antigos para economizar espaço
Com essa configuração, os logs do contêiner ocupam no máximo 30 MB (10 MB × 3). Ao ultrapassar 10 MB, o Docker cria um novo arquivo; quando há mais de três arquivos, o mais antigo é removido ou compactado.
Os arquivos de log ficam em /var/lib/docker/containers/<container-id>/<container-id>-json.log. Use o comando du para verificar o espaço ocupado:
du -sh /var/lib/docker/containers/*/*-json.log
Escolha do driver
O Docker oferece vários drivers de logs: json-file, syslog, fluentd, journald, local e outros. Para a maioria dos cenários, json-file ou local são suficientes.
A documentação oficial do Docker informa que o driver local é mais eficiente que json-file e já inclui rotação de logs, sem exigir a configuração manual de max-size/max-file. Se o volume de logs for muito alto, como dezenas de GB por dia, considere usar local:
logging:
driver: "local"
O driver local, porém, tem uma desvantagem: não permite consultar diretamente o conteúdo com docker logs. Para manter a compatibilidade, é necessário adicionar mode: "non-blocking" à configuração.
Coleta centralizada de logs (opcional)
Em uma implantação de servidor único, json-file ou local são suficientes. Porém, com dezenas de servidores e centenas de contêineres, gerenciar logs espalhados se torna difícil. Nesse caso, considere uma solução centralizada:
- Fluentd: coleta de logs leve, adequada a clusters pequenos
- ELK Stack (Elasticsearch + Logstash + Kibana): solução poderosa, mas com alto custo de implantação
- Loki + Grafana: opção nativa da nuvem, bem integrada ao ecossistema Prometheus
Essas soluções exigem configurações mais complexas e estão fora do escopo deste artigo. Como exemplo, esta é a ideia básica de uma configuração com Fluentd:
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "docker.{{.Name}}"
O Fluentd encaminha os logs ao endereço configurado, permitindo coletá-los e analisá-los de forma unificada em outro servidor.
Modelo completo de configuração
Ao combinar health checks, políticas de reinicialização e gerenciamento de logs, você obtém um docker-compose.yml adequado para produção. O exemplo completo abaixo inclui um banco de dados PostgreSQL, um cache Redis e um serviço de API:
version: '3.8'
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
api:
build:
context: ./api
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://myuser:mypassword@postgres:5432/mydb
REDIS_URL: redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
deploy:
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
compress: "true"
volumes:
postgres_data:
Principais pontos da configuração
Ordem de inicialização: o depends_on do contêiner api aguarda simultaneamente a aprovação dos health checks de postgres e redis. A API só inicia quando o banco de dados e o cache estão prontos, evitando erros de conexão durante a inicialização.
Diferença no tamanho dos logs: postgres e redis geralmente geram poucos logs, então 10 MB × 3 é suficiente; o serviço de API pode gerar mais e usa 50 MB × 5. Ajuste os valores ao volume real de logs, em vez de aplicar o mesmo limite a todos.
Diferença no atraso de reinicialização: o postgres demora para iniciar e precisa de tempo para se recuperar após uma reinicialização, por isso o delay é de 5 segundos; a API inicia rapidamente, mas usa um delay de 10 segundos para dar mais margem ao health check.
Período de aquecimento: start_period: 30s para o postgres, dando tempo suficiente à inicialização do banco; start_period: 5s para o redis, que inicia rapidamente; e start_period: 10s para a API, pois alguns segundos costumam bastar para iniciar a aplicação.
Você pode copiar esse modelo diretamente e substituir apenas as variáveis de ambiente e as imagens pelas do seu projeto. Se houver outros serviços, como MongoDB ou MinIO, siga o mesmo padrão e acrescente health checks, políticas de reinicialização e configurações de logs.
Armadilhas comuns e diagnóstico
Mesmo depois de concluir a configuração e a implantação, ainda podem surgir problemas. Veja algumas armadilhas comuns e como investigá-las.
O health check sempre falha
Sintoma: o status do contêiner permanece como unhealthy, embora a aplicação pareça funcionar normalmente.
Etapas de diagnóstico:
-
Primeiro, verifique se a ferramenta usada pelo comando de health check existe:
docker exec <container> which curl docker exec <container> which pg_isreadyImagens Alpine frequentemente não incluem curl; instale-o manualmente ou use wget.
-
Execute manualmente o comando de health check e confira a saída:
docker exec <container> curl -f http://localhost:8080/healthSe houver erro, o próprio endpoint de health check pode estar com problema.
-
Consulte o estado detalhado do health check:
docker inspect --format='{{json .State.Health}}' <container> | jqA saída mostra os resultados das verificações recentes, as causas das falhas e os horários.
O contêiner reinicia repetidamente
Sintoma: o contêiner inicia, encerra alguns segundos depois e os logs ficam cheios de registros de reinicialização.
Etapas de diagnóstico:
-
Verifique a causa da saída do contêiner:
docker inspect --format='{{.State.ExitCode}}' <container> docker inspect --format='{{.State.Error}}' <container>O código de saída indica aproximadamente o tipo de problema (1 = erro geral, 137 = processo encerrado por OOM, 139 = falha de segmentação).
-
Consulte a quantidade de reinicializações:
docker inspect --format='{{.RestartCount}}' <container>Se o número for muito alto, verifique se
max_attemptsestá funcionando. -
Consulte os logs do contêiner para encontrar o erro específico:
docker logs --tail 100 <container>
O disco ficou cheio por causa dos logs
Sintoma: o sistema alerta sobre pouco espaço em disco e o diretório /var/lib/docker/containers ocupa muito espaço.
Etapas de diagnóstico:
-
Encontre os maiores arquivos de log:
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -5 -
Confira se a configuração de rotação de logs está funcionando:
docker inspect --format='{{.HostConfig.LogConfig}}' <container>Se a saída mostrar
Config: {}, a rotação de logs não foi configurada. -
Limpe os logs manualmente como solução temporária:
truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.logEssa é uma solução temporária. A correção permanente é configurar a rotação de logs.
Lista de comandos para diagnóstico rápido
Quando surgir um problema, estes comandos ajudam a identificar a causa rapidamente:
# Mostra o estado de saúde de todos os contêineres
docker ps --format "table {{.Names}}\t{{.Status}}"
# Mostra o histórico de health checks de um contêiner
docker inspect --format='{{json .State.Health}}' <container>
# Mostra o código de saída e a quantidade de reinicializações
docker inspect --format='ExitCode: {{.State.ExitCode}}, RestartCount: {{.RestartCount}}' <container>
# Mostra o tamanho dos arquivos de log
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh
# Mostra as 100 entradas de log mais recentes do contêiner
docker logs --tail 100 <container>
Conclusão
Ao implantar o Docker Compose em produção, estas três configurações não são opcionais, mas essenciais: health checks fazem o contêiner mais do que apenas parecer ativo; políticas de reinicialização dão ao serviço uma chance de se recuperar automaticamente sem entrar em um loop infinito; e o gerenciamento de logs evita que o disco fique cheio.
Checklist das configurações essenciais:
- Health check:
test+interval+timeout+retries+start_period - Política de reinicialização:
condition: on-failure+max_attempts: 3 - Rotação de logs:
max-size: 10m+max-file: 3+compress: true
Plano de ação em três etapas:
- Verifique seu docker-compose.yml atual e confirme se as três configurações estão presentes. Se alguma faltar, adicione pelo menos o health check e a rotação de logs.
- Use o modelo completo acima para implantar um serviço de teste e observe se o health check funciona e se os logs estão sendo rotacionados.
- Guarde os comandos de diagnóstico. Na próxima vez que um alerta chegar às três da manhã, você poderá localizar rapidamente o problema.
Não deixe seus contêineres desprotegidos em produção. Configure essas três camadas de proteção para que o serviço possa se recuperar automaticamente, o diagnóstico seja rápido e os logs não ocupem todo o disco.
FAQ
Como definir valores adequados para interval e timeout no health check?
É melhor usar restart: always ou on-failure?
Qual deve ser o tamanho dos arquivos de log e quantos devem ser mantidos?
O health check do contêiner sempre falha, mas a aplicação funciona normalmente. O que fazer?
Para que serve condition: service_healthy em depends_on?
Como verificar rapidamente quanto espaço em disco os logs dos contêineres ocupam?
14 min de leitura · Publicado em: 12 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
Erros no Docker Compose: como diagnosticar e resolver os 5 mais comuns
Aprenda a diagnosticar erros no Docker Compose, como conflitos de porta, falhas de rede e build, contêineres encerrados e problemas de permissão.
Parte 10 de 34
Próximo
Três pilares do Docker Compose em produção: healthcheck, política de reinicialização e limites de recursos
Entenda os três pilares do Docker Compose em produção: healthchecks para confirmar que os serviços estão prontos, políticas de reinicialização para autorrecuperação e limites de recursos para evitar sobrecarga. Inclui um modelo YAML completo para criar aplicações conteinerizadas estáveis e confiáveis.
Parte 12 de 34



Comentários
Entre com GitHub para comentar