Docker do Zero ao Profissional
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_perioderetriesforam 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 --waittermina 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.