Guia: implementar health checks e readiness probes para aplicações PHP em Docker Compose
Por Kevin Marcondes · Publicado em · 9 min de leitura
Este guia mostra como implementar health checks e readiness probes para aplicações PHP em Docker Compose. Você verá a diferença entre liveness e readiness, como expor endpoints mínimos, configurar docker-compose.yml, integrar Nginx e cobrir workers de fila. Incluímos exemplos práticos e dicas de calibração para produção.
Conceitos: liveness, readiness e como isso se aplica no Docker Compose
Antes de configurar, alinhe os termos. Liveness verifica se o processo ainda está vivo. Readiness indica se a aplicação pode receber tráfego com segurança. No Kubernetes há checks separados; no Docker Compose existe um único healthcheck por serviço, mas conseguimos emular readiness com endpoints e regras de roteamento.
Com checks bem definidos, você evita deploys quebrados, erros intermitentes em boot, e loops de reinício. Também reduz incidentes após migrações ou quando dependências externas oscilam.
Para visualizar estados de saúde com interface, usar uma GUI como Portainer ajuda na inspeção dos serviços.
- Liveness: mata/reinicia quando o processo travou.
- Readiness: segura o tráfego até dependências ficarem prontas.
- Startup: dá tempo extra antes de avaliar a saúde.
| Conceito | O que verifica | Ação quando falha | No Docker Compose |
|---|---|---|---|
| Liveness | Processo está responsivo | Reiniciar o container | healthcheck + policy de restart |
| Readiness | Pode receber tráfego | Retirar do balanceador / segurar tráfego | Endpoint /ready + reverse proxy; depends_on: service_healthy |
| Startup | Conseguiu inicializar | Aguardar antes de checar | start_period no healthcheck; scripts wait-for |
Endpoints mínimos em PHP: /health (liveness) e /ready (readiness)
Endpoints HTTP simples funcionam bem. /health retorna 200 quando o processo está vivo. /ready retorna 200 somente se dependências essenciais (por exemplo, banco e cache) estiverem acessíveis e no estado esperado.
Mantenha os checks rápidos. Evite consultas pesadas. Use timeouts curtos. Não exponha detalhes sensíveis na resposta. Se possível, limite esses endpoints à rede interna do Compose.
- /health: sem dependências externas, resposta rápida.
- /ready: testa conexões leves a banco, cache, fila.
- Respostas planas (texto) e códigos HTTP claros: 200 ou 503.
<?php
// public/healthz.php
// Um único script que responde /health e /ready
header('Content-Type: text/plain');
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/health', PHP_URL_PATH);
if ($path === '/health') {
echo 'OK';
exit(0);
}
if ($path === '/ready') {
$errors = [];
// Banco (ex.: Postgres)
try {
$dsn = sprintf(
'pgsql:host=%s;port=%s;dbname=%s',
getenv('DB_HOST') ?: 'db',
getenv('DB_PORT') ?: '5432',
getenv('DB_NAME') ?: 'app'
);
$pdo = new PDO($dsn, getenv('DB_USER') ?: 'app', getenv('DB_PASS') ?: 'secret', [PDO::ATTR_TIMEOUT => 2]);
$pdo->query('SELECT 1');
} catch (Throwable $e) {
$errors[] = 'db';
}
// Cache (ex.: Redis)
try {
if (class_exists('Redis')) {
$r = new Redis();
$r->connect(getenv('REDIS_HOST') ?: 'redis', (int)(getenv('REDIS_PORT') ?: 6379), 1.5);
$r->ping();
}
} catch (Throwable $e) {
$errors[] = 'redis';
}
if ($errors) {
http_response_code(503);
echo 'NOT_READY: ' . implode(',', $errors);
exit(1);
}
echo 'READY';
exit(0);
}
http_response_code(404);
echo 'not found';
docker-compose.yml com healthcheck, start_period e depends_on
No Docker Compose, defina healthcheck por serviço. Use start_period para dar tempo de aquecimento antes da avaliação. Emule readiness conectando o Nginx apenas quando o app estiver saudável com depends_on: condition: service_healthy.
O exemplo abaixo usa: app (PHP-FPM), nginx, db e redis. O app expõe um healthcheck via CLI para readiness. O Nginx faz liveness via HTTP. Para imagens enxutas, veja multistage builds.
- Use retries para evitar falsos negativos.
- Calibre interval, timeout e start_period.
- Defina restart policies apropriadas (ex.: unless-stopped).
version: "3.9"
services:
app:
image: php:8.2-fpm-alpine
working_dir: /var/www/html
volumes:
- ./:/var/www/html
environment:
DB_HOST: db
DB_PORT: "5432"
DB_NAME: app
DB_USER: app
DB_PASS: secret
REDIS_HOST: redis
REDIS_PORT: "6379"
healthcheck:
test: ["CMD-SHELL", "php /var/www/html/health/ready-cli.php"]
interval: 10s
timeout: 3s
retries: 5
start_period: 25s
restart: unless-stopped
nginx:
image: nginx:alpine
ports: ["8080:80"]
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./:/var/www/html:ro
depends_on:
app:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost/health || exit 1"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
restart: unless-stopped
redis:
image: redis:7
restart: unless-stopped
Workers de fila e cron: healthcheck e heartbeat confiáveis
Workers não têm endpoint HTTP. Faça o healthcheck executar um script CLI que valide dependências e um heartbeat de progresso. A cada N segundos, o worker atualiza um arquivo ou chave no Redis. O healthcheck falha se o heartbeat congelar.
Isso diferencia pausa natural (sem jobs) de travamento. Em frameworks como Laravel, você pode incluir estatísticas no script (ex.: tamanho da fila).
- Atualize um heartbeat com timestamp a cada iteração.
- No healthcheck, rejeite se o heartbeat ficar velho.
- Valide conexões a Redis/DB com timeouts curtos.
<?php
// health/worker_health.php
// 1) Worker deve atualizar este arquivo/chave com timestamps.
// 2) Este script roda no healthcheck do container do worker.
$now = time();
$maxAge = (int)(getenv('HEARTBEAT_MAX_AGE') ?: 60); // segundos
$hbFile = getenv('HEARTBEAT_FILE') ?: '/tmp/worker_heartbeat';
$errors = [];
try {
// Ex.: validar Redis rapidamente
if (class_exists('Redis')) {
$r = new Redis();
$r->connect(getenv('REDIS_HOST') ?: 'redis', (int)(getenv('REDIS_PORT') ?: 6379), 1.5);
$r->ping();
}
} catch (Throwable $e) {
$errors[] = 'redis';
}
$age = is_file($hbFile) ? ($now - filemtime($hbFile)) : PHP_INT_MAX;
if ($age > $maxAge) {
$errors[] = 'stale_heartbeat';
}
if ($errors) {
fwrite(STDERR, 'UNHEALTHY: '.implode(',', $errors)."\n");
exit(1);
}
echo "OK\n";
Calibrando interval, timeout, retries e start_period
Valores ruins geram falsos negativos ou alertas lentos. Ajuste conforme performance do app e do ambiente.
Boas práticas para começar e depois refinar com dados reais:
- interval: 10s a 30s para serviços de produção.
- timeout: 2s a 5s. Se maior que a latência típica, investigue.
- retries: 3 a 5 para amortecer oscilações pontuais.
- start_period: cubra o tempo de boot + aquecimento do cache.
- Diferencie /health (rápido) e /ready (com dependências).
Gate de tráfego com Nginx e FPM ping (readiness sem orquestrador)
Sem Kubernetes, você ainda consegue readiness: sirva /health estático e /ready passando ao PHP. Enquanto /ready não retornar 200, faça o Nginx responder 503, evitando tráfego precoce. Opcionalmente, habilite o ping do PHP-FPM e monitore-o.
Veja a doc. do PHP-FPM sobre ping e status em php.net: https://www.php.net/manual/en/install.fpm.configuration.php.
- /health: resposta estática 200 no Nginx.
- /ready: rota PHP que testa DB/Redis e retorna 200/503.
- FPM ping opcional para liveness interno.
server {
listen 80;
server_name _;
root /var/www/html/public;
# Liveness simples e rápido
location = /health { return 200 "OK"; add_header Content-Type text/plain; }
# Readiness: só 200 quando app estiver realmente pronto
location = /ready {
fastcgi_pass app:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/healthz.php;
fastcgi_param REQUEST_URI /ready;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass app:9000;
}
}
# Opcional (no php-fpm www.conf do container app):
# ping.path = /fpm-ping
# ping.response = pong
# pm.status_path = /fpm-status
Observabilidade: registre e monitore seus checks
Logs e métricas ajudam a explicar por que um serviço ficou unhealthy. Centralize logs dos containers e crie um painel com séries dos healthchecks e retornos de /ready.
Para montar rapidamente um stack de métricas no Compose, confira o guia de Prometheus e Grafana. Para logs estruturados em apps PHP, veja Fluentd + Elasticsearch.
Se você usa proxy dinâmico, o comparativo Traefik vs Nginx pode ajudar a decidir onde aplicar gates de readiness.
Como validar que funcionou e troubleshooting comum
Depois de subir o ambiente, valide manualmente e force falhas para observar o comportamento. Teste também o caminho do worker.
Erros comuns costumam ser fáceis de corrigir quando você sabe onde olhar.
- Falha por timeout alto/baixo: ajuste timeout e retries.
- /ready lento: simplifique os testes e adicione timeouts de conexão.
- Endpoint exposto publicamente: restrinja a rede ou firewall.
- Service names errados no Compose: alinhe hosts com nomes dos serviços.
- Nginx apontando para script incorreto: revise SCRIPT_FILENAME.
# Suba o stack
docker compose up -d
# Verifique estados de saúde (Compose v2)
docker compose ps
# Teste endpoints pelo Nginx
curl -i http://localhost:8080/health
curl -i http://localhost:8080/ready
# Force uma falha de readiness (pare o Redis)
docker compose stop redis
curl -i http://localhost:8080/ready # deve retornar 503
Perguntas frequentes
Qual a diferença prática entre /health e /ready em Docker Compose?
/health verifica se o processo está vivo e responde rápido, sem tocar dependências externas. /ready checa se a aplicação já pode receber tráfego, como conexão a banco e cache. No Compose você usa healthcheck para liveness e gates no Nginx e depends_on para readiness.
Preciso de Kubernetes para readiness de verdade?
Não. Em Compose você emula readiness com um endpoint /ready, Nginx retornando 503 enquanto não estiver pronto e depends_on: condition: service_healthy para ordenar start de serviços consumidores. Não é tão sofisticado quanto um orquestrador, mas cobre a necessidade da maioria dos projetos.
Devo colocar autenticação nos endpoints de health?
Em geral, não. Health checks devem ser rápidos e simples. Prefira restringir acesso pela rede (somente dentro do cluster/Compose) ou por firewall. Evite expor publicamente. Nunca retorne detalhes sensíveis, apenas códigos e mensagens mínimas.
Como testar banco e Redis sem deixar o /ready lento?
Use operações leves com timeouts curtos, como SELECT 1 e PING, e evite consultas pesadas. Faça as conexões com timeouts de 1–3 segundos e falhe rápido. Se o ambiente tem picos de latência, aumente retries do healthcheck para reduzir falsos negativos.
Como monitorar os health checks ao longo do tempo?
Colete logs dos endpoints e crie métricas nos seus painéis. Uma opção prática é subir Prometheus e Grafana com Compose para acompanhar variações e falhas. Veja o guia de métricas com Compose disponível em /configurar-prometheus-grafana-docker-compose-vps.
Posso usar o ping do PHP-FPM em vez de /health?
Sim. O ping do FPM é útil para liveness do processo PHP-FPM. Habilite ping.path e exponha uma rota no Nginx que encaminhe para o FPM. Para readiness completo, mantenha um /ready que valide dependências do aplicativo.
Quer revisar seus health checks antes do próximo deploy?
Compartilhe seu docker-compose.yml e o desenho do seu app PHP. Podemos avaliar endpoints, calibração e Nginx para reduzir falhas em produção.
Conversar sobre o seu ambienteVeja também
- Guia prático: como configurar failover ativo-passivo entre duas VPS com Keepalived, HAProxy e lsyncd para uploads sincronizados
- Guia: checklist técnico para preparar VPS Debian com Docker Compose para aplicações PHP em produção
- Guia prático para dimensionamento de VPS e tuning de PHP‑FPM para lojas Magento/OpenMage com picos sazonais: do sizing ao teste de carga
- guia prático: reconstruir índices no PostgreSQL em produção usando CREATE INDEX CONCURRENTLY e índices parciais (passo a passo seguro)