Implantação com Docker — Containerização e Builds Multi-Stage
Docker é como um contêiner de transporte — empacota aplicações e todas as suas dependências em contêineres padrão que podem ser descarregados e executados diretamente em qualquer porta (servidor), eliminando o problema "funciona na minha máquina."
1. O Que Você Vai Aprender
- Dockerfile multi-stage: Estágio de build (instalando dependências UV) vs. Estágio de execução (emagrecendo a imagem)
- Orquestração com Docker Compose: Stack Completa de FastAPI + PostgreSQL + Redis + Celery Worker
- Variáveis de Ambiente e Gerenciamento de Segredos: Arquivos
.enve Docker Secrets - Health Check: Comando
HEALTHCHECKe Endpoint/healthdo FastAPI - Cenário Alice: Charlie lança o serviço full-stack do PriceTracker com um único clique
docker compose up
2. A História Real da Alice
(1) Problema: Falhas de implantação causadas por diferenças de ambiente
Quando Charlie implantou o PriceTracker — que havia configurado localmente — no servidor de produção, encontrou problemas como incompatibilidade de versão do Python, biblioteca libpq ausente, UV não instalado e configurações de conexão PostgreSQL diferentes — forçando-o a gastar duas horas resolvendo manualmente cada implantação. Para piorar, os quatro serviços — FastAPI, PostgreSQL, Redis e Celery — tinham que ser iniciados e gerenciados separadamente, e sua ordem de inicialização era complexa e interdependente.
(2) A Solução Docker Compose
O Dockerfile define contêineres de aplicação, enquanto o Docker Compose orquestra todos os serviços e suas dependências. Um único comando docker compose up lança toda a stack de serviços, e a consistência do ambiente é garantida pela imagem.
(3) Resultado
O tempo de implantação passou de um processo manual de 2 horas para 30 segundos com um único clique, e o ambiente de desenvolvimento local agora é idêntico ao de produção, então Charlie nunca mais terá que dizer "Funciona no meu servidor."
3. Dockerfile Multi-Stage
(1) Processo de Build
flowchart LR
A[Builder Stage] -->|Copiar deps instaladas| B[Runtime Stage]
subgraph Builder
A1[UV install deps] --> A2[Compile wheels]
end
subgraph Runtime
B1[Python slim image] --> B2[Copiar código da app]
B2 --> B3[Copiar deps do builder]
B3 --> B4[Executar uvicorn]
end
(1) ▶Exemplo: Dockerfile Multi-Stage
# === Estágio 1: Builder ===
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
WORKDIR /app
# Copiar arquivos de dependência primeiro (camada de cache)
COPY pyproject.toml uv.lock ./
# Instalar dependências no ambiente virtual
RUN uv sync --frozen --no-dev --no-install-project
# Copiar código da aplicação
COPY app/ app/
# === Estágio 2: Runtime ===
FROM python:3.12-slim-bookworm AS runtime
WORKDIR /app
# Instalar dependências de sistema em runtime
RUN apt-get update && \
apt-get install -y --no-install-recommends libpq5 && \
rm -rf /var/lib/apt/lists/*
# Copiar ambiente virtual do builder
COPY --from=builder /app/.venv /app/.venv
# Copiar código da aplicação
COPY app/ app/
# Definir variáveis de ambiente
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
# Executar aplicação
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Saída:
// Execução Bem-sucedida
(2) Comparação de Tamanhos de Imagem
| Método | Tamanho da Imagem | Descrição |
|---|---|---|
| Estágio único (Python 3.12) | ~1.2 GB | Inclui ferramentas de build e cache |
| Multi-stage (Python:3.12-slim) | ~200 MB | Apenas dependências de runtime |
| Alpine (python:3.12-alpine) | ~80MB | Menor mas tem problemas de compatibilidade |
4. Orquestração com Docker Compose
(1) Arquitetura Completa da Stack
flowchart TD
Nginx[Nginx Reverse Proxy] --> API1[FastAPI Worker 1]
Nginx --> API2[FastAPI Worker 2]
API1 --> PG[(PostgreSQL)]
API2 --> PG
API1 --> Redis[(Redis)]
API2 --> Redis
API1 --> Broker[Redis Broker]
API2 --> Broker
Broker --> CW1[Celery Worker 1]
Broker --> CW2[Celery Worker 2]
CW1 --> PG
CW2 --> PG
CW1 --> Redis
CW2 --> Redis
Flower[Flower Monitor] --> Broker
(1) ▶Exemplo: docker-compose.yml
version: "3.8"
services:
api:
build:
context: .
dockerfile: docker/Dockerfile
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql+asyncpg://pricetracker:${DB_PASSWORD}@postgres:5432/pricetracker
- REDIS_URL=redis://redis:6379/0
- CELERY_BROKER_URL=redis://redis:6379/1
- SECRET_KEY=${SECRET_KEY}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: pricetracker
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: pricetracker
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pricetracker"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
celery-worker:
build:
context: .
dockerfile: docker/Dockerfile
command: celery -A app.core.celery_app worker --loglevel=info --concurrency=4
environment:
- DATABASE_URL=postgresql+asyncpg://pricetracker:${DB_PASSWORD}@postgres:5432/pricetracker
- REDIS_URL=redis://redis:6379/0
- CELERY_BROKER_URL=redis://redis:6379/1
depends_on:
- redis
- postgres
flower:
build:
context: .
dockerfile: docker/Dockerfile
command: celery -A app.core.celery_app flower --port=5555
ports:
- "5555:5555"
depends_on:
- redis
volumes:
postgres_data:
redis_data:
Saída:
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
(2) ▶Exemplo: Arquivo .env
# .env - Docker Compose lê este arquivo automaticamente
DB_PASSWORD=change-me-in-production
SECRET_KEY=your-very-long-random-secret-key-at-least-32-chars
CELERY_BROKER_URL=redis://redis:6379/1
Saída:
// Execução Bem-sucedida
5. Health Checks e Sequência de Inicialização
(1) depends_on + healthcheck
O depends_on + condition: service_healthy no Docker Compose garantem a ordem correta de inicialização dos serviços: PostgreSQL e Redis ficam prontos primeiro, e depois o FastAPI inicia.
| Serviços | Ordem de Inicialização | Health Checks |
|---|---|---|
| PostgreSQL | 1 (Primeiro) | pg_isready -U pricetracker |
| Redis | 1 (Primeiro) | redis-cli ping |
| FastAPI | 2 (Após PG+Redis estiver pronto) | GET /health |
| Celery Worker | 3 (após Redis e PostgreSQL estiverem prontos) | Heartbeat interno |
(1) ▶Exemplo: Endpoint de Health Check do FastAPI
@app.get("/health")
async def health_check(db: AsyncSession = Depends(get_db), redis: Redis = Depends(get_redis)):
# Verificar conectividade do banco de dados
try:
await db.execute(select(1))
db_status = "healthy"
except Exception:
db_status = "unhealthy"
# Verificar conectividade do Redis
try:
await redis.ping()
redis_status = "healthy"
except Exception:
redis_status = "unhealthy"
overall = "healthy" if db_status == "healthy" and redis_status == "healthy" else "unhealthy"
return {
"status": overall,
"database": db_status,
"redis": redis_status,
}
Saída:
# Função definida com sucesso
❓Perguntas Frequentes
entrypoint.sh, execute alembic upgrade head primeiro antes de iniciar o uvicorn..env (adicionados ao .gitignore). Para produção, use Docker secrets ou K8s Secrets; nunca os codifique diretamente ou os comita no Git.(2 x núcleos CPU) + 1. Para um servidor de 4 núcleos, defina 9 workers. No entanto, você também deve considerar limitações de memória; cada worker usa 50-100 MB.docker compose logs api para visualizar logs do FastAPI, docker compose logs -f para monitorar em tempo real. Em ambientes de produção, use ELK/Loki para agregar logs.📖Resumo
- Dockerfile multi-stage: Dependências são instaladas durante o estágio Builder, e apenas os resultados são copiados durante o estágio Runtime, reduzindo o tamanho da imagem de 1.2 GB para 200 MB
- Orquestração Full-Stack com Docker Compose: API + PostgreSQL + Redis + Celery Worker + Flower
depends_on+healthcheckGarante a seguinte sequência de inicialização: o banco de dados fica pronto primeiro, seguido pela API- Arquivo
.envpara gerenciar variáveis de ambiente; use Docker secrets no ambiente de produção - O health check verifica o status de conexão do banco de dados e Redis do endpoint; o comando
HEALTHCHECKrealiza detecção automática
📝Exercícios
- Problema Básico (Dificuldade ⭐): Escreva um Dockerfile de estágio único para o PriceTracker, construa a imagem, execute o contêiner e verifique se o endpoint
/healthestá acessível. Dica:FROM python:3.12-slim+CMD ["uvicorn", ...] - Exercício Avançado (Dificuldade ⭐⭐): Modifique o Dockerfile para usar build multi-stage, e escreva um arquivo
docker-compose.ymlque inclui três serviços: API, PostgreSQL e Redis, para que possam ser iniciados com um único comando:docker compose up. Dica:AS builder+COPY --from=builder - Desafio (Dificuldade ⭐⭐⭐): Complete a orquestração Docker Compose — adicione serviços Celery Worker e Flower, adicione health check, defina uma senha no arquivo
.env, e adicioneentrypoint.shpara executar antes da inicializaçãoalembic upgrade head. Dica:depends_on+condition: service_healthy+ script entrypoint
---|



