Guia: implementar health checks e readiness probes para aplicações PHP em Docker Compose

Por · Publicado em · 9 min de leitura

Blocos de vidro quadrados com reflexos azulados formando um padrão geométrico
Textura moderna e neutra, adequada como fundo para conteúdo técnico sobre infraestrutura. Foto: Chris F / Pexels

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 ambiente

Veja também