Healthcheck failed
Sintoma:
[FAIL] meu-app Phase 1.4: failed health check after 30sOu no modo HTTP:
[FAIL] meu-app /health returned 502 after 30s (3/3 retries)Causa
O container do deploy novo subiu, mas a probe configurada no manifest (http/tcp/cmd) não retornou OK no prazo definido. O Runner é blue-green: ele mantém o container antigo servindo tráfego até o novo passar no health. Por isso, healthcheck failed = rollback automático, seu site continua no ar.
Diagnóstico
runner status meu-app # container state atual (running/stopped)
runner logs tail meu-app # logs do container que falhou
runner debug last-error meu-app # último DeployFailure estruturado
runner health meu-app # roda a probe manualmente uma vezOlha primeiro o debug last-error — ele já vem com a mensagem de
erro e fix sugerido.
v2.50.0+: o rollback destrói o container falho, mas antes o runner
arquiva as últimas 200 linhas de docker logs em
<logs_dir>/failures/<app>/<version>_<timestamp>.log (default
/opt/runner/logs/failures/...). A mensagem de erro do deploy aponta o
caminho exato. Não use docker logs no container que sobrou — é o da
versão anterior, que voltou no rollback.
O último probe e a sugestão
O erro do deploy traz o resultado do último probe que reprovou, lido do container antes de ele ser removido:
último probe: modo cmd · alvo `curl -f http://localhost:8080/health` · exit 127
saída: /bin/sh: curl: not found
sugestão: a imagem não tem `curl`; use `tcp: 8080`- modo e alvo:
httpcom o path,tcpcom a porta,cmdcom o comando. - exit: o código do probe.
127é comando não encontrado;-1é o Docker que nem conseguiu rodar o probe (ex.: a imagem não temsh). - saída: até 300 caracteres. Os segredos da app (
secrets:,.secrets, cofre) e tokens com cara de token saem como***.
Quando o probe registrado é o HEALTHCHECK da própria imagem (modos
external_* e container_state, que não injetam sonda, ou quando o deploy
honrou o HEALTHCHECK da imagem), a linha diz modo HEALTHCHECK da imagem,
o alvo é o comando dele e não há sugestão: o manifesto não decide esse probe.
O mesmo vai no evento deploy_failure, no campo ultimo_probe
(modo, alvo, exit, saida; modo: imagem para o HEALTHCHECK da
imagem); o campo não aparece quando não houve probe. O last_error da app
(runner status) continua começando por "health check did not pass", com o
último probe e a sugestão depois.
A sugestão aparece quando a causa é reconhecível:
| O probe disse | Sugestão |
|---|---|
exit 127, sh: curl: not found ou exec: "curl": executable file not found |
a imagem não tem curl; use tcp: <porta> |
exec: "/bin/sh": … no such file or directory (imagem scratch/distroless), em qualquer modo (cmd, path: ou tcp:) |
a imagem não tem sh; use mode: external_tcp com tcp: <porta> (o tcp: também roda com sh; o external_tcp testa a porta de fora do container) |
a sonda path: do runner sai 127 |
a imagem não tem wget, curl nem python; use tcp: <porta> |
Connection refused com localhost no cmd, em imagem Alpine ou com ::1 na saída |
troque localhost por 127.0.0.1 (o wget do busybox resolve localhost para ::1) |
| resposta 401 ou 403 | o path está atrás de autenticação; aponte o healthcheck para um path sem login, como /health |
O runner manifest validate (sem --estatico) já acusa antes do deploy a
imagem sem shell e o localhost em Alpine; o "não tem curl" só aparece
na hora do probe.
Fix — cenário 1: timeout muito curto
Se a app demora 40s pra subir mas o healthcheck tem timeout 30s:
# .deploy.yml
healthcheck:
mode: http
path: /health
timeout: 60s # default 30s
retries: 3Commit + push na branch dist → próximo fetch --deploy aplica.
Fix — cenário 2: app demora pra bootar (migrations, cache warm)
Use start_period pra ignorar falhas durante o boot:
healthcheck:
mode: http
path: /health
start_period: 60s # ignora primeiros 60sFix — cenário 3: endpoint errado ou modo errado
Confirme que a app expõe /health:
docker exec -it $(docker ps -q --filter name=meu-app) curl localhost:8080/healthSe não expõe, mude pra mode: tcp (TCP connect) ou crie o endpoint.
Veja Healthcheck modes.
Fix — cenário 4: app realmente não sobe (crash loop)
runner logs history meu-app # ver logs do último container paradoSe for bug de código: corrige + push. Se for env var faltando: veja Secrets lifecycle.
Como prevenir
- Teste localmente com
docker build && docker runantes de push. - Configure
start_periodse a app tem migrations no boot. - Use
mode: tcpse HTTP é overkill — a probe TCP é mais leve. - Configure notificações (Notificações) para ser avisado de fails sem precisar checar manualmente.
Conceito relacionado
Por que o site continuou no ar? Veja GitOps mode — o Runner não substitui o container até o health passar.