Dependências no Docker Compose: healthcheck para resolver a ordem de inicialização do banco

Sexta-feira, dez da noite, e as mesmas mensagens de erro já tinham rolado no terminal mais de dez vezes.
O contêiner da aplicação reiniciava sem parar. O banco até iniciava, mas sempre demorava um pouco mais. Conferi o docker-compose.yml: depends_on estava configurado. Então por que não funcionava?
Esse problema já atrapalhou muita gente. No ambiente local, as duas primeiras tentativas com docker-compose up falham, e só depois de alguns reinícios e uns dez segundos tudo começa a funcionar.
A causa é simples: o depends_on do Docker controla a ordem de inicialização dos contêineres, mas não verifica se o serviço está realmente pronto.
Neste artigo, você vai aprender:
- as três condições de
depends_on— 90% das pessoas conhecem apenas a padrão; - como configurar corretamente o
healthcheckdo PostgreSQL e do MySQL, com exemplos completos; - uma alternativa moderna aos scripts
wait-for-it; - um checklist de diagnóstico para inicializar seus contêineres com estabilidade.
Por que depends_on não é suficiente: iniciar não significa estar pronto
A documentação oficial do Docker traz uma frase importante, mas fácil de ignorar:
Compose does not wait until a container is “ready”, only until it’s running.
Em outras palavras: o Compose espera o contêiner iniciar, mas não espera o serviço ficar realmente disponível.
A diferença entre iniciar o contêiner e disponibilizar o serviço
Imagine o processo de inicialização de um contêiner PostgreSQL:
- 0 segundo: o Docker inicia o contêiner e o processo
postgres←depends_onjá libera a aplicação aqui - 2 segundos: inicialização do diretório de dados
- 5 segundos: carregamento dos arquivos de configuração
- 8 segundos: execução dos scripts de
init, se houver - 12 segundos: finalmente fica pronto para aceitar conexões
Há uma diferença de 12 segundos. Se a aplicação web tentar conectar no primeiro segundo, o resultado inevitável será Connection refused.
Já encontrei casos ainda mais extremos. Em um projeto legado que mantive, o script de inicialização do banco precisava importar 500 MB de dados de teste, um processo que levava 40 segundos. Com o depends_on padrão, o contêiner da aplicação precisava falhar e reiniciar pelo menos cinco vezes antes de conseguir conectar.
As três condições de depends_on
Muita gente não sabe que depends_on aceita três condições:
services:
web:
depends_on:
db:
condition: service_started # Padrão: basta iniciar o contêiner
# condition: service_healthy # Aguarda o healthcheck passar
# condition: service_completed_successfully # Aguarda o contêiner encerrar com sucesso (adequado para init)
service_started (padrão): continua assim que o contêiner entra no estado running. É por isso que o problema persiste mesmo com depends_on configurado.
service_healthy: aguarda o healthcheck passar e o contêiner entrar no estado healthy. É o comportamento de que realmente precisamos.
service_completed_successfully: aguarda o contêiner encerrar com sucesso, com código de saída 0. É adequada para tarefas executadas uma única vez, como migrações de dados.
Por que service_healthy não é o padrão?
Talvez você esteja se perguntando: se service_healthy é tão útil, por que não é a opção padrão?
Há dois motivos:
- nem todo serviço precisa de um healthcheck, como um worker totalmente stateless;
- você precisa definir o healthcheck, pois o Docker não sabe o que significa “estar pronto” para seu serviço.
Isso nos leva à próxima questão: como configurar o healthcheck.
Guia completo de configuração do healthcheck
O princípio do healthcheck é direto: o Docker executa um comando periodicamente. Se ele retornar 0, o serviço é considerado saudável; se retornar 1, é considerado não saudável.
Configuração completa de healthcheck
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"] # Comando de verificação
interval: 10s # Verifica a cada 10 segundos
timeout: 5s # Cada verificação expira após 5 segundos
retries: 3 # Marca como unhealthy após 3 falhas
start_period: 30s # Falhas nos primeiros 30 segundos não contam em retries
Esses cinco parâmetros são importantes. Vamos analisar cada um.
test: comando de verificação
Há dois formatos:
# Opção 1: usar o shell (recomendada)
test: ["CMD-SHELL", "pg_isready -U postgres"]
# Opção 2: executar o comando diretamente, sem passar pelo shell
test: ["CMD", "pg_isready", "-U", "postgres"]
Na maioria dos casos, CMD-SHELL é suficiente, pois permite usar recursos do shell, como pipes e redirecionamento.
Armadilha comum: a ferramenta usada no comando precisa existir na imagem. Se você usar curl para testar um endpoint HTTP, mas a imagem não tiver curl, o healthcheck sempre falhará. Eu mesmo já perdi meia hora até perceber que precisava adicionar RUN apk add curl ao Dockerfile.
interval: intervalo entre verificações
Define a frequência das verificações. Um intervalo muito curto desperdiça recursos; um muito longo demora para detectar mudanças.
- 10 segundos é um bom padrão para a maioria dos casos;
- para serviços críticos, como bancos de dados, você pode usar 5 segundos;
- para serviços leves, 15 a 30 segundos também funciona.
timeout: tempo limite de cada verificação
É o tempo máximo para uma verificação. Se o comando travar, o Docker aguarda esse período antes de desistir.
Um valor muito curto pode gerar falsos positivos; um valor muito longo atrasa a detecção de falhas. De 5 a 10 segundos é uma faixa segura.
retries: número de tentativas após falhas
Define quantas falhas consecutivas são necessárias para marcar o contêiner como unhealthy.
É um mecanismo contra oscilações. Uma instabilidade de rede ou uma breve recarga do banco pode causar uma falha isolada, e retries torna o sistema mais resistente.
De 3 a 5 tentativas costuma ser razoável. retries=1 é sensível demais; retries=10, lento demais.
start_period: período de tolerância na inicialização
Este é o parâmetro mais fácil de ignorar e uma fonte frequente de problemas.
As falhas durante start_period não contam em retries. Na prática, ele oferece um período de tolerância para o serviço iniciar.
Por que isso importa? Bancos de dados levam tempo para subir. O PostgreSQL precisa inicializar o diretório de dados; o MySQL precisa carregar os índices das tabelas. Sem start_period, o healthcheck pode começar a contabilizar falhas no segundo 2 e marcar o serviço como unhealthy antes mesmo de ele terminar a inicialização.
Valores recomendados:
- PostgreSQL/MySQL: 30 a 60 segundos;
- serviços leves, como Redis: 15 a 30 segundos;
- serviços com muitos scripts de inicialização: até 120 segundos.
Costumo usar 60 segundos. Prefiro esperar um pouco mais a gerar um alerta incorreto.
Erros comuns e como evitá-los
Erro 1: referência incorreta a uma variável de ambiente
# ❌ Incorreto: o Compose interpola antes de iniciar, usando o valor da variável no host
test: ["CMD", "mysqladmin", "ping", "-p$MYSQL_ROOT_PASSWORD"]
# ✅ Correto: use $$ para que o shell do contêiner faça a expansão
test: ["CMD-SHELL", "mysqladmin ping -p$$MYSQL_ROOT_PASSWORD"]
Erro 2: start_period curto demais
# ❌ O banco ainda não terminou de iniciar, mas as falhas já são contabilizadas
healthcheck:
test: ["CMD", "pg_isready"]
interval: 5s
retries: 3
start_period: 10s # Curto demais!
# ✅ Dê tempo suficiente para a inicialização
healthcheck:
start_period: 60s # Bem mais seguro
Erro 3: a ferramenta de verificação não existe
Esse erro é especialmente traiçoeiro, porque o Docker apenas registra a falha.
# ❌ Sem curl na imagem, o healthcheck sempre falhará
test: ["CMD", "curl", "-f", "http://localhost/health"]
# ✅ Garanta que a ferramenta exista ou use uma já presente na imagem
test: ["CMD", "wget", "--spider", "http://localhost/health"] # Imagens Alpine incluem wget
Com o healthcheck configurado, vejamos como aplicá-lo a bancos de dados específicos.
Healthcheck do PostgreSQL na prática
A imagem oficial do PostgreSQL inclui uma ferramenta muito útil: pg_isready.
Ela foi criada especificamente para verificar se o PostgreSQL está pronto e é mais confiável do que escrever uma consulta SQL improvisada.
Configuração básica recomendada
version: '3.8'
services:
web:
image: node:20-alpine
depends_on:
db:
condition: service_healthy # Importante: aguarda o healthcheck passar
restart: true # Reinicia a aplicação quando o banco reiniciar
environment:
DATABASE_URL: postgresql://postgres:password@db:5432/myapp
command: npm start
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Entendendo o comando pg_isready
pg_isready -U postgres -d myapp
-U: define o usuário, que precisa existir;-d: define o banco de dados, opcional, mas recomendado.
Por que incluir -U? Sem essa opção, pg_isready tenta conectar com o usuário atual do sistema e gera vários avisos no log. Isso não impede o funcionamento, mas polui a saída.
Configuração avançada com uma consulta real
pg_isready verifica se a porta está acessível, mas não garante que o banco execute consultas. Para uma verificação mais rigorosa:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres && psql -U postgres -d myapp -c 'SELECT 1'"]
interval: 10s
timeout: 10s # Aumente o timeout por causa da consulta adicional
retries: 3
start_period: 60s
SELECT 1 é a consulta mais simples possível. Se ela for executada, o banco não apenas iniciou, como também consegue processar SQL normalmente.
Para a maioria dos casos, porém, o pg_isready básico é suficiente.
Resultado da execução
Depois de configurar, inicie os serviços:
$ docker-compose up
Creating network "myapp_default" ... done
Creating myapp_db_1 ... done
Waiting for myapp_db_1 to be healthy... ← Observe esta linha
Creating myapp_web_1 ... done
db_1 | PostgreSQL init process complete; ready for start up.
db_1 | database system is ready to accept connections
web_1 | Server listening on port 3000 ← A aplicação só inicia quando o banco está pronto
Você verá uma pausa perceptível: o Docker aguarda o contêiner db ficar healthy. Isso pode levar de 30 a 60 segundos, mas em troca a aplicação inicia sem falhas.
Diagnóstico: o contêiner permanece unhealthy
Se o banco continuar no estado unhealthy, consulte o log do healthcheck:
# Ver o estado de saúde do contêiner
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq
{
"Status": "unhealthy",
"FailingStreak": 5,
"Log": [
{
"Start": "2024-12-17T03:15:30Z",
"End": "2024-12-17T03:15:30Z",
"ExitCode": 1,
"Output": "pg_isready: could not connect to server: Connection refused"
}
]
}
As causas mais comuns são:
start_periodcurto demais: as falhas começam a ser contabilizadas antes de o banco terminar a inicialização;- usuário ou nome do banco incorreto:
pg_isreadynão consegue conectar; - falha na inicialização do PostgreSQL: consulte o log com
docker logs myapp_db_1.
Healthcheck do MySQL na prática
Para o MySQL, usamos mysqladmin ping, a ferramenta administrativa incluída no próprio MySQL.
Configuração básica recomendada
version: '3.8'
services:
web:
image: node:20-alpine
depends_on:
db:
condition: service_healthy
restart: true
environment:
DATABASE_URL: mysql://root:password@db:3306/myapp
command: npm start
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: myapp
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Entendendo o comando mysqladmin ping
mysqladmin ping -h localhost -u root -ppassword
-h: endereço do host; dentro do contêiner, uselocalhost;-u: nome do usuário;-p: senha — observe que não há espaço entre-pe a senha.
Quando o MySQL está funcionando, o comando retorna:
mysqld is alive
O código de saída é 0, e o healthcheck passa.
Como tratar a senha corretamente
Opção 1: escrever a senha diretamente — adequada para desenvolvimento
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
É simples, mas deixa a senha fixa na configuração.
Opção 2: usar uma variável de ambiente — recomendada
db:
environment:
MYSQL_ROOT_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
# Atenção: use $$, não $
Aqui existe uma armadilha: você precisa usar $$, e não $.
Por quê? O Docker Compose interpreta variáveis de ambiente antes de iniciar o contêiner. Com $MYSQL_ROOT_PASSWORD, ele procura a variável no host, não dentro do contêiner. $$ diz ao Compose para não expandi-la e deixar essa tarefa para o shell do contêiner.
Opção 3: verificação sem senha — a mais simples, mas controversa
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
Algumas configurações do MySQL permitem conexão local sem senha. Nesse caso, é a solução mais simples, mas não é recomendada em produção.
Cuidados específicos com o MySQL 8.0
O MySQL 8.0 usa por padrão o plugin de autenticação caching_sha2_password, que pode ser incompatível com clientes antigos. Se sua aplicação apresentar erro de autenticação, você pode forçar o método anterior:
db:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: myapp
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
Problemas comuns
Problema 1: Access denied for user 'root'@'localhost'
A senha está incorreta ou a variável de ambiente não foi aplicada. Verifique:
- se
MYSQL_ROOT_PASSWORDestá escrito corretamente; - se a senha do healthcheck é a mesma;
- se você usou
$$para escapar a variável.
Problema 2: o contêiner demora para iniciar e permanece em starting
A inicialização do diretório de dados do MySQL leva tempo, principalmente na primeira execução. Defina um start_period suficiente; 60 segundos costuma bastar. Se um script de init importar muitos dados, podem ser necessários 120 segundos ou mais.
Problema 3: o healthcheck passa, mas a aplicação não conecta ao banco
Pode ser um problema de rede ou da configuração da aplicação. Verifique:
- se a string de conexão está correta — use
dbcomo host, nãolocalhost; - se a rede do Docker está configurada corretamente;
- com
docker network inspect, se os contêineres estão na mesma rede.
Healthchecks para outros bancos de dados e serviços
Depois de entender PostgreSQL e MySQL, você pode aplicar o mesmo princípio a outros serviços. Veja uma referência rápida.
Redis
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 15s
redis-cli ping retorna PONG com código de saída 0. Como o Redis inicia rapidamente, o start_period pode ser menor.
MongoDB
mongo:
image: mongo:7
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
A partir do MongoDB 6.0, mongosh substituiu o antigo comando mongo. Para versões anteriores:
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]
RabbitMQ
rabbitmq:
image: rabbitmq:3-management-alpine
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 10s
timeout: 5s
retries: 3
start_period: 40s
O RabbitMQ demora mais para iniciar, então recomenda-se um start_period de pelo menos 40 segundos.
Serviços HTTP em geral
Se o serviço oferece um endpoint de saúde, como /health ou /ping, use wget ou curl:
api:
image: myapp:latest
healthcheck:
test: ["CMD", "wget", "--spider", "--quiet", "http://localhost:8080/health"]
# Ou use curl, caso exista na imagem
# test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
--spider faz o wget apenas verificar o recurso, sem baixá-lo; --quiet suprime a saída.
Atenção: confirme se a imagem contém wget ou curl. Imagens Alpine normalmente incluem wget; imagens Debian/Ubuntu costumam incluir curl.
Serviços sem uma ferramenta específica
Se o serviço não oferece uma ferramenta de healthcheck, use netcat (nc) para testar a porta:
service:
image: some-service:latest
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 9000 || exit 1"]
interval: 10s
timeout: 3s
retries: 3
start_period: 30s
nc -z apenas verifica se a porta está aberta, sem estabelecer uma conexão completa. Esse método não confirma que o serviço está realmente pronto, portanto é menos preciso do que os anteriores.
O script wait-for-it ainda é necessário?
Ao pesquisar sobre ordem de inicialização no Docker, você provavelmente encontrará muitos artigos recomendando os scripts wait-for-it ou wait-for.
A ideia é acrescentar uma espera ao entrypoint do contêiner da aplicação, verificando se a porta TCP do serviço dependente está acessível.
Abordagem tradicional com wait-for-it
web:
image: node:20-alpine
depends_on:
- db # depends_on simples, sem verificar o estado de saúde
volumes:
- ./wait-for-it.sh:/wait-for-it.sh # Monta o script
command: ["/wait-for-it.sh", "db:5432", "--", "npm", "start"]
O wait-for-it.sh verifica repetidamente a porta db:5432 e só executa npm start quando consegue conectar.
Por que essa abordagem deixou de ser recomendada?
A boa prática em 2024 é: se puder usar o healthcheck nativo, não use um script.
Os motivos são:
- configuração mais clara: a lógica de saúde fica junto ao serviço de banco, deixando a dependência visível;
- nenhum arquivo adicional: você não precisa manter um script nem montar um volume;
- verificação mais completa: o healthcheck pode testar se o serviço realmente está pronto, enquanto uma porta TCP aberta é um sinal superficial;
- melhor reutilização: configure o healthcheck uma vez, e todos os serviços dependentes se beneficiam.
Um artigo bastante conhecido na comunidade resume a ideia no título: “Forget wait-for-it, use docker-compose healthcheck and depends_on instead”.
Quando wait-for-it ainda é necessário?
Há duas situações especiais.
Situação 1: não é possível modificar a imagem ou a configuração do Compose
Por exemplo, você usa uma imagem de terceiros sem healthcheck e não tem permissão para alterar a configuração. Nesse caso, adicionar wait-for-it no lado da aplicação pode ser a única opção.
Situação 2: é preciso aguardar vários serviços
./wait-for-it.sh db:5432 redis:6379 rabbitmq:5672 -- npm start
Embora depends_on também aceite vários serviços, essa sintaxe de wait-for-it pode ser mais concisa. Ainda assim, esse cenário não é tão comum.
Outras ferramentas alternativas
Além de wait-for-it, existem ferramentas parecidas:
- dockerize: escrita em Go, oferece mais recursos e suporta templates com variáveis de ambiente;
- wait-for: versão simplificada de
wait-for-it, implementada somente em shell; - docker-compose-wait: escrita em Python, com suporte a verificações HTTP.
Mas, na prática, em 2024 vale a regra: se o healthcheck atende ao caso, não complique com essas ferramentas.
Checklist de diagnóstico e boas práticas
Configurou tudo e ainda não funciona? Siga este checklist; ele resolve 90% dos problemas.
Comandos para um diagnóstico rápido
# 1. Ver o estado de todos os contêineres
$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
myapp_db postgres db healthy 5432/tcp
myapp_web npm start web running 0.0.0.0:3000->3000/tcp
# 2. Ver os detalhes do healthcheck
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq
# 3. Ver os logs dos contêineres
$ docker-compose logs db
$ docker-compose logs web
# 4. Acompanhar os logs em tempo real
$ docker-compose logs -f --tail=100
Árvore de decisão para problemas comuns
Problema: o contêiner permanece em starting e nunca fica healthy
- verifique se
start_periodé curto demais → tente 60s; - confira se o comando do healthcheck está correto → use
docker inspectpara ver o comando executado; - entre no contêiner e execute o comando manualmente →
docker exec -it myapp_db_1 pg_isready -U postgres.
Problema: o contêiner alterna entre unhealthy e healthy
intervalpode estar curto demais para os recursos disponíveis → aumente para 10s ou 15s;retriespode estar baixo demais e uma falha eventual já dispara o estado → aumente para 5;- o banco pode realmente ter um problema de desempenho → consulte os logs do banco.
Problema: o healthcheck passa, mas a aplicação ainda não conecta ao banco
- confira a string de conexão → o host deve ser o nome do serviço, como
db, nãolocalhost; - confira o mapeamento de portas → a comunicação entre contêineres usa a porta interna, como 5432, não a porta publicada;
- confira a rede → confirme que todos os serviços estão na mesma
network.
Boas práticas para produção
1. Valores recomendados — configuração conservadora
healthcheck:
interval: 10s # Equilíbrio entre resposta e consumo de recursos
timeout: 5s # Tempo suficiente para o comando executar
retries: 5 # Tolera falhas eventuais
start_period: 60s # Dá tempo suficiente para o banco iniciar
Esses parâmetros são estáveis na maioria dos cenários. Se o banco tiver muitos scripts de inicialização, aumente start_period para 120s.
2. Use uma política de restart
web:
depends_on:
db:
condition: service_healthy
restart: true # Reinicia a aplicação quando o banco reiniciar
restart: unless-stopped # Reinicia automaticamente quando o contêiner encerrar
restart: true garante que, após uma atualização ou reinicialização do banco, os serviços dependentes também reiniciem e estabeleçam uma nova conexão.
3. Limites de recursos
Healthchecks também consomem recursos, embora pouco. Se o sistema estiver no limite:
healthcheck:
interval: 30s # Aumenta o intervalo entre verificações
timeout: 3s # Reduz o timeout
Na prática, o custo do healthcheck costuma ser insignificante, a menos que você execute centenas de contêineres.
4. Monitore o estado de saúde
Em produção, recomenda-se acompanhar o estado dos healthchecks com uma ferramenta de monitoramento. Eventos de saúde do Docker podem ser coletados por ferramentas como Prometheus e Grafana.
# docker-compose.yml
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
labels:
- "prometheus.io/scrape=true" # Permite que o Prometheus colete o estado
5. Configurações por ambiente
Desenvolvimento e produção podem usar valores diferentes:
# docker-compose.yml (desenvolvimento)
db:
healthcheck:
start_period: 30s # Poucos dados no desenvolvimento, inicialização rápida
# docker-compose.prod.yml (produção)
db:
healthcheck:
start_period: 120s # Mais dados em produção, inicialização lenta
interval: 5s # Verificações mais frequentes
Ao iniciar, informe os arquivos de configuração:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
Técnicas de depuração
Técnica 1: teste o comando do healthcheck manualmente
Entre no contêiner e execute o comando para descobrir o erro exato:
$ docker exec -it myapp_db_1 sh
/# pg_isready -U postgres -d myapp
/var/run/postgresql:5432 - accepting connections
/# echo $?
0 ← Código 0 indica sucesso
Técnica 2: desative o healthcheck temporariamente
Durante a depuração, comente o healthcheck e use um depends_on simples para descartar um problema na própria verificação:
web:
depends_on:
- db # Modo simples temporário
# db:
# condition: service_healthy
Depois de confirmar que a aplicação conecta normalmente ao banco, reative o healthcheck.
Técnica 3: consulte o log de eventos do Docker
O Docker registra todos os eventos dos contêineres, incluindo mudanças no estado de saúde:
$ docker events --filter 'event=health_status'
2024-12-17T03:15:30.123456789Z container health_status: healthy (name=myapp_db_1)
2024-12-17T03:16:45.987654321Z container health_status: unhealthy (name=myapp_db_1)
Isso ajuda a identificar o momento em que o contêiner se tornou unhealthy e relacioná-lo aos timestamps dos logs.
Conclusão
Voltando à pergunta do início: por que a aplicação falha mesmo com depends_on configurado?
Porque ele, sozinho, não é suficiente.
Por padrão, depends_on controla apenas a inicialização do contêiner, não a disponibilidade do serviço. Um contêiner de banco no estado running ainda pode não aceitar conexões, e essa diferença é a origem do problema.
A solução é direta:
- configure um healthcheck no banco, usando
pg_isreadyoumysqladmin pingpara testar a disponibilidade real; - use a condição
service_healthypara a aplicação aguardar a aprovação do healthcheck; - defina um
start_periodrazoável, começando por 60 segundos, para dar tempo suficiente à inicialização do banco.
Com essas três medidas, as falhas de inicialização dos contêineres praticamente desaparecem.
Para colocar em prática:
- agora: copie a configuração de PostgreSQL ou MySQL do artigo para seu projeto e ajuste as variáveis de ambiente;
- hoje: atualize os
depends_ondos projetos da equipe paraservice_healthy; - na próxima reunião: compartilhe a solução e padronize a configuração da equipe.
Se você encontrou um caso não abordado aqui, deixe um comentário. O Docker Compose tem muitas armadilhas, e podemos resolvê-las juntos.
Por fim, se este artigo ajudou a corrigir um problema antigo de inicialização, deixe sua curtida. É esse momento de “finalmente funcionou” que motiva a produção deste conteúdo.
Fluxo completo para configurar healthcheck no Docker Compose
Resolva falhas de inicialização causadas por um banco de dados ainda indisponível, com modelos completos para PostgreSQL e MySQL.
⏱️ Estimated time: 30 min
- 1
Step 1: Entenda a limitação de depends_on
A raiz do problema:
• O depends_on do Docker controla apenas a ordem de inicialização dos contêineres, não se o serviço está realmente pronto.
• O Compose espera o contêiner iniciar, mas não verifica se o serviço já pode ser usado.
• A documentação oficial do Docker diz: Compose does not wait until a container is "ready", only until it's running.
• Em outras palavras: o Compose espera o contêiner iniciar, não o serviço ficar disponível.
Diferença entre iniciar o contêiner e disponibilizar o serviço:
• 0 segundo: o Docker inicia o contêiner e o processo postgres; depends_on já libera a aplicação.
• 2 segundos: inicialização do diretório de dados.
• 5 segundos: carregamento dos arquivos de configuração.
• 8 segundos: execução dos scripts de init, se houver.
• 12 segundos: o banco finalmente fica pronto para aceitar conexões.
Há uma diferença de 12 segundos. Se a aplicação web tentar conectar no primeiro segundo, o resultado será Connection refused. - 2
Step 2: Conheça as três condições de depends_on
As três condições de depends_on:
1. service_started (padrão)
• Espera apenas o contêiner iniciar, sem verificar se o serviço está pronto.
• Não é recomendada para esse caso.
2. service_healthy (recomendada)
• Espera o contêiner iniciar e o healthcheck passar.
• Garante que o serviço esteja realmente pronto.
3. service_completed_successfully
• Espera o contêiner concluir a execução e encerrar com sucesso.
• É adequada para tarefas executadas uma única vez.
Use service_healthy:
• No docker-compose.yml, configure depends_on: db: condition: service_healthy.
• Assim, a aplicação só inicia quando o banco de dados estiver realmente pronto. - 3
Step 3: Configure corretamente o healthcheck do PostgreSQL e do MySQL
Healthcheck do PostgreSQL:
• Use o comando pg_isready para verificar se o banco está pronto.
• Exemplo:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s (tempo suficiente para inicializar o banco)
Healthcheck do MySQL:
• Use mysqladmin ping para verificar se o banco está pronto.
• Exemplo:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s
Configuração completa:
• Defina o healthcheck no docker-compose.yml, incluindo comando, intervalo, timeout e número de tentativas.
• Use a condição service_healthy em depends_on.
• Garanta que a aplicação só inicie quando o banco estiver realmente pronto. - 4
Step 4: Faça o diagnóstico e aplique as boas práticas
Checklist de diagnóstico:
1. Verifique se o comando do healthcheck está correto.
• Execute o comando manualmente dentro do contêiner e confirme que o código de saída é 0.
• Exemplo: docker exec -it db_container pg_isready -U postgres.
2. Desative o healthcheck temporariamente.
• Durante a depuração, comente o healthcheck.
• Use o depends_on simples para descartar um problema no próprio healthcheck.
3. Consulte os eventos do Docker.
• Use docker events para acompanhar eventos de inicialização e de healthcheck.
Boas práticas:
1. Configure healthcheck no banco com pg_isready ou mysqladmin ping para testar a disponibilidade real.
2. Use a condição service_healthy para a aplicação aguardar a aprovação do healthcheck.
3. Defina um start_period razoável, começando por 60 segundos, para dar tempo ao banco inicializar.
Com essas três medidas, as falhas de inicialização dos contêineres praticamente desaparecem.
Uma alternativa moderna aos scripts wait-for-it:
• Use a combinação depends_on + healthcheck.
• Não exige scripts adicionais, tem configuração simples e boa confiabilidade.
• É adequada para projetos Docker Compose.
FAQ
Por que depends_on não é suficiente? Qual é a diferença entre o contêiner iniciar e o serviço ficar pronto?
A documentação oficial do Docker traz uma observação importante: Compose does not wait until a container is "ready", only until it's running. Ou seja, o Compose espera o contêiner iniciar, não o serviço ficar disponível.
Imagine o processo de inicialização de um contêiner PostgreSQL:
• 0 segundo: o Docker inicia o contêiner e o processo postgres; depends_on já libera a aplicação.
• 2 segundos: inicialização do diretório de dados.
• 5 segundos: carregamento dos arquivos de configuração.
• 8 segundos: execução dos scripts de init, se houver.
• 12 segundos: o banco finalmente fica pronto para aceitar conexões.
Há uma diferença de 12 segundos. Se a aplicação web tentar conectar no primeiro segundo, o resultado será Connection refused.
Quais são as três condições disponíveis em depends_on?
1) service_started (padrão):
• Espera apenas o contêiner iniciar, sem verificar se o serviço está pronto.
• Não é recomendada para esse caso.
2) service_healthy (recomendada):
• Espera o contêiner iniciar e o healthcheck passar.
• Garante que o serviço esteja realmente pronto.
• No docker-compose.yml, configure depends_on: db: condition: service_healthy.
3) service_completed_successfully:
• Espera o contêiner concluir a execução e encerrar com sucesso.
• É adequada para tarefas executadas uma única vez.
Recomenda-se service_healthy para iniciar a aplicação somente quando o banco estiver realmente pronto.
Como configurar o healthcheck do PostgreSQL e do MySQL?
• Use pg_isready para verificar se o banco está pronto.
• Exemplo:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s (tempo suficiente para inicializar o banco)
MySQL:
• Use mysqladmin ping para verificar se o banco está pronto.
• Exemplo:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s
Defina o healthcheck no docker-compose.yml com comando, intervalo, timeout e tentativas, e use service_healthy em depends_on para que a aplicação só inicie quando o banco estiver pronto.
Como diagnosticar problemas no healthcheck?
1) Verifique se o comando do healthcheck está correto:
• Execute o comando manualmente dentro do contêiner e confirme que o código de saída é 0.
• Exemplo: docker exec -it db_container pg_isready -U postgres.
• O código 0 indica sucesso.
2) Desative o healthcheck temporariamente:
• Durante a depuração, comente o healthcheck.
• Use o depends_on simples para descartar um problema no próprio healthcheck.
• Depois de confirmar que a aplicação conecta ao banco, reative o healthcheck.
3) Consulte os eventos do Docker:
• Use docker events para acompanhar eventos de inicialização e mudanças no healthcheck.
Quais são as boas práticas para configurar healthchecks?
1) Configure healthcheck no banco com pg_isready ou mysqladmin ping para testar a disponibilidade real.
2) Use a condição service_healthy para a aplicação aguardar a aprovação do healthcheck.
3) Defina um start_period razoável, começando por 60 segundos, para dar tempo ao banco inicializar.
Com essas três medidas, as falhas de inicialização dos contêineres praticamente desaparecem.
Uma alternativa moderna aos scripts wait-for-it:
• Use a combinação depends_on + healthcheck.
• Não exige scripts adicionais, tem configuração simples e boa confiabilidade.
• É adequada para projetos Docker Compose.
Ações rápidas:
• Agora: copie a configuração de PostgreSQL ou MySQL do artigo para seu projeto e ajuste as variáveis de ambiente.
• Hoje: atualize os depends_on dos projetos da equipe para service_healthy.
• Na próxima reunião: compartilhe a solução e padronize a configuração da equipe.
18 min de leitura · Publicado em: 17 dez 2025 · 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 com vários serviços: inicie o ambiente de desenvolvimento local com um único comando
Use o Docker Compose para orquestrar vários serviços e iniciar Web, API, MySQL e Redis no ambiente de desenvolvimento local com um único comando. Evite instalações manuais trabalhosas, conflitos de versão e portas ocupadas; novos integrantes conseguem clonar o repositório e começar a desenvolver em 5 minutos, além de alternar entre projetos em segundos.
Parte 8 de 34
Próximo
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



Comentários
Entre com GitHub para comentar