Docker Healthcheck: containers confiáveis com Compose

Aprenda a detectar containers indisponíveis, coordenar dependências no Docker Compose e validar a saúde da aplicação antes de liberar o ambiente.

Compartilhe

Três containers conectados com monitoramento de saúde e sinal luminoso de disponibilidade
This entry is in the series Docker do Zero ao Profissional

Docker do Zero ao Profissional

Três containers conectados com monitoramento de saúde e sinal luminoso de disponibilidade

Docker Healthcheck: containers confiáveis com Compose

O que é Docker e por que desenvolvedores deveriam aprender agora

O que é Docker e por que todo dev deveria usar

Como instalar o Docker no Windows, Linux e macOS – Guia completo

Instalando o Docker no Windows, Linux e macOS – Guia completo e fácil

Primeiros comandos Docker na prática – Guia rápido para iniciantes

Primeiros comandos Docker na prática: seu primeiro container em minutos

Como criar sua primeira imagem Docker com Dockerfile

Criando sua primeira imagem Docker com Dockerfile

Como usar volumes e redes no Docker para salvar dados e conectar containers

Entendendo volumes, redes e persistência de dados no Docker

Docker Compose - como orquestrar múltiplos containers com um só comando

Docker Compose – Organizando Múltiplos Serviços Facilmente

Boas práticas com Docker – Performance, Segurança e Organização

Boas práticas com Docker – Performance, Segurança e Organização

Um container em execução não é necessariamente um serviço disponível. O processo pode continuar ativo enquanto a aplicação trava, perde acesso ao banco ou deixa de responder às requisições. Se o ambiente verifica apenas se o container está “de pé”, essa falha permanece invisível até um usuário reclamar.

O HEALTHCHECK acrescenta um sinal objetivo: o Docker executa um teste dentro do container e registra os estados starting, healthy ou unhealthy. Com o Docker Compose, esse sinal também pode coordenar a inicialização de serviços dependentes e permitir que um deploy espere o ambiente ficar realmente pronto.

Nesta aula 8 da série Docker do Zero ao Profissional, vamos evoluir o projeto apresentado em Docker Compose: organizando múltiplos serviços. O exemplo usa uma API Python e PostgreSQL, mas o raciocínio vale para qualquer aplicação composta por vários containers.

Processo ativo não significa aplicação saudável

O Docker já sabe se o processo principal terminou. O healthcheck responde a outra pergunta: o serviço ainda consegue cumprir sua função mínima? Para uma API, isso costuma significar aceitar uma conexão HTTP e responder rapidamente. Para um banco, pode significar aceitar conexões. Para um worker, pode ser confirmar acesso à fila e a recursos essenciais.

Uma verificação útil deve ser:

  • rápida, para não consumir recursos nem acumular processos;
  • determinística, retornando sucesso ou falha sem ambiguidade;
  • local, testando o serviço pelo ponto de vista do próprio container;
  • representativa, sem executar uma operação pesada ou destrutiva;
  • segura, sem expor dados sensíveis na resposta.

Evite usar apenas a existência do processo, porque isso repete o que o runtime já observa. Também evite chamar serviços externos desnecessários: se a API de um terceiro ficar fora do ar, todos os seus containers podem parecer doentes ao mesmo tempo.

A documentação do Docker informa que o comando do healthcheck sinaliza o resultado pelo código de saída. Na prática, 0 representa sucesso e 1 indica falha. Após o número configurado de falhas consecutivas, o status passa a unhealthy.

Crie endpoints de vida e prontidão

Em aplicações profissionais, é útil separar dois conceitos. Liveness confirma que o processo responde. Readiness confirma que ele está pronto para receber tráfego e acessar dependências indispensáveis.

Em uma API Flask, a estrutura pode começar assim:

from flask import Flask, jsonify
from sqlalchemy import text
from database import engine

app = Flask(__name__)

@app.get("/health/live")
def live():
    return jsonify(status="ok"), 200

@app.get("/health/ready")
def ready():
    try:
        with engine.connect() as connection:
            connection.execute(text("SELECT 1"))
        return jsonify(status="ready"), 200
    except Exception:
        return jsonify(status="not_ready"), 503

O endpoint de vida não consulta o banco; ele apenas prova que a aplicação responde. O de prontidão executa uma consulta mínima e devolve 503 quando a dependência essencial não está disponível. Não retorne stack traces, credenciais ou detalhes internos da infraestrutura.

Para um projeto pequeno, o healthcheck do container pode chamar /health/ready. Em ambientes maiores, a separação permite que a plataforma reinicie um processo que deixou de responder sem tratar uma indisponibilidade temporária do banco como falha fatal da aplicação.

Configure o HEALTHCHECK no Dockerfile

O teste pode fazer parte da imagem, seguindo a mesma evolução iniciada na aula Criando sua primeira imagem com Dockerfile. Como a imagem do exemplo já possui Python, podemos usar a biblioteca padrão e evitar instalar curl apenas para a verificação:

FROM python:3.13-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

HEALTHCHECK --interval=30s --timeout=5s \
  --start-period=20s --retries=3 \
  CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=3)"]

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Cada parâmetro controla uma parte do comportamento:

  • interval: intervalo entre verificações;
  • timeout: tempo máximo permitido para cada tentativa;
  • start-period: período de tolerância para a aplicação inicializar;
  • retries: quantidade de falhas consecutivas antes de marcar como não saudável.

Os valores precisam refletir o comportamento real do serviço. Um start-period curto demais gera falsos negativos durante migrações ou aquecimento. Um intervalo muito longo demora a revelar falhas. Um timeout exagerado mantém verificações presas e reduz a utilidade do sinal.

Há apenas um HEALTHCHECK efetivo por Dockerfile; se a instrução aparecer mais de uma vez, somente a última vale. Você também pode usar HEALTHCHECK NONE para desativar uma verificação herdada da imagem base.

Coordene banco e API no Docker Compose

O Compose permite definir ou sobrescrever o healthcheck de cada serviço. A forma longa de depends_on adiciona a condição service_healthy, fazendo a API esperar o banco ficar saudável antes de ser criada:

services:
  db:
    image: postgres:18
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s
    restart: unless-stopped

  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://app:${POSTGRES_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

volumes:
  postgres_data:

A sintaxe curta depends_on: [db] garante a ordem de criação, mas não espera o banco aceitar conexões. service_healthy usa o resultado do healthcheck para fechar essa lacuna. Isso reduz erros de inicialização, mas a aplicação ainda deve implementar tentativas com espera progressiva: dependências podem cair depois que o ambiente já iniciou.

Mantenha a senha em um arquivo .env fora do Git ou, em produção, em um gerenciador de segredos. O healthcheck do PostgreSQL não precisa repetir a senha porque pg_isready apenas verifica o estado de aceitação de conexões.

Entenda o limite das políticas de reinício

restart: unless-stopped e HEALTHCHECK resolvem problemas diferentes. A política de reinício atua quando o processo do container termina; o healthcheck registra se um processo ainda ativo consegue atender ao teste. Um container marcado como unhealthy não é reiniciado automaticamente pelo Docker Engine apenas por causa desse estado.

As políticas mais comuns são:

  • no: não reinicia automaticamente;
  • on-failure: reinicia quando o processo termina com código diferente de zero;
  • always: reinicia sempre, respeitando as regras de parada manual do Docker;
  • unless-stopped: reinicia, exceto quando o container foi interrompido explicitamente.

Se a aplicação detectar uma condição irrecuperável, encerrar o processo com erro permite que on-failure ou unless-stopped atue. Para decisões mais sofisticadas — substituir instâncias doentes, limitar tentativas e distribuir tráfego — use um orquestrador compatível com essas necessidades. Não instale um supervisor dentro do container apenas para mascarar falhas; a documentação do Docker recomenda usar as políticas do runtime.

Valide o ambiente e provoque uma falha controlada

Antes de considerar o trabalho concluído, valide a configuração e espere os serviços ficarem saudáveis:

docker compose config
docker compose up --build --wait --wait-timeout 90
docker compose ps

A opção --wait aguarda os serviços atingirem o estado running ou healthy e implica modo destacado. Isso é especialmente útil em scripts de deploy e integração contínua, pois o comando falha em vez de seguir adiante com um ambiente incompleto.

Para inspecionar o histórico de uma verificação:

docker inspect --format '{{json .State.Health}}' projeto-api-1
docker compose logs --tail=100 api db

Em um ambiente local, pare o banco por alguns segundos e observe a API mudar de estado. Depois, inicie o banco novamente e confirme a recuperação. Esse teste revela se timeouts, tentativas e endpoints representam o comportamento real, sem esperar a primeira falha em produção.

Use este checklist antes do deploy:

  • [ ] O teste verifica uma capacidade real do serviço, não apenas o processo?
  • [ ] O endpoint responde rápido e não revela informações sensíveis?
  • [ ] interval, timeout, start_period e retries foram ajustados ao tempo real de inicialização?
  • [ ] Dependências críticas usam condition: service_healthy?
  • [ ] A aplicação tolera a queda de uma dependência depois da inicialização?
  • [ ] A política de reinício escolhida corresponde ao comportamento esperado?
  • [ ] docker compose up --wait termina com sucesso no pipeline?
  • [ ] Uma falha controlada foi observada nos estados e nos logs?

Com healthchecks bem definidos, o Docker deixa de informar apenas que processos existem e passa a mostrar se os serviços entregam a capacidade esperada. O próximo passo é levar essa confiabilidade ao processo de entrega: automatizar build, testes e publicação da imagem sem perder rastreabilidade. Enquanto isso, revise também as boas práticas de performance, segurança e organização no Docker para consolidar a base da aplicação.

Docker do Zero ao Profissional

O que é Docker e por que todo dev deveria usar

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.