404 Not Found

404 Not Found


nginx

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


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

100%
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

DOCKERFILE
# === 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:

TEXT
// 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

100%
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

YAML
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:

TEXT
CONTAINER ID   IMAGE          STATUS         PORTS
abc123         nginx:latest   Up 2 hours     0.0.0.0:80->80/tcp

(2) ▶Exemplo: Arquivo .env

INI
# .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:

TEXT
// 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

PYTHON
@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:

TEXT
# Função definida com sucesso

❓Perguntas Frequentes

P Por que usar um build multi-stage?
R Uma imagem Python completa (incluindo o compilador) é usada durante o estágio de build, enquanto uma imagem slim é usada em runtime. A imagem final não contém nenhuma ferramenta de build, é 5 a 10 vezes menor, e tem uma superfície de ataque menor.
P O Docker Compose é adequado para produção?
R É adequado para projetos de pequeno e médio porte. Para ambientes de produção em grande escala, use Kubernetes ou Docker Swarm. Compose é adequado para desenvolvimento, testes e implantações em pequena escala.
P Como lidar com migrações de banco de dados?
R Execute as migrações antes do contêiner da API iniciar. Adicione um init container, ou em entrypoint.sh, execute alembic upgrade head primeiro antes de iniciar o uvicorn.
P Como os segredos são transmitidos com segurança?
R Para desenvolvimento, use arquivos .env (adicionados ao .gitignore). Para produção, use Docker secrets ou K8s Secrets; nunca os codifique diretamente ou os comita no Git.
P Como definir o número de workers?
R Fórmula: (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.
P Como visualizar logs de contêineres?
R 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


📝Exercícios

  1. 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 /health está acessível. Dica: FROM python:3.12-slim + CMD ["uvicorn", ...]
  2. Exercício Avançado (Dificuldade ⭐⭐): Modifique o Dockerfile para usar build multi-stage, e escreva um arquivo docker-compose.yml que 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
  3. 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 adicione entrypoint.sh para executar antes da inicialização alembic upgrade head. Dica: depends_on + condition: service_healthy + script entrypoint

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%