FastAPI com Docker na VPS

    FastAPI é o framework Python mais performático para APIs REST — usa tipagem estática, gera documentação automática (Swagger) e tem performance comparável a Node.js. Deploy na VPS com Docker usa Gunicorn como process manager e Uvicorn como worker ASGI, com Nginx na frente para TLS e caching.

    Dockerfile para FastAPI em produção

    Imagem Python slim com dependências instaladas via pip e usuário não-root:

    dockerfile
    # Dockerfile
    FROM python:3.12-slim
    
    WORKDIR /app
    
    # Instalar dependências do sistema (mínimo necessário)
    RUN apt-get update && apt-get install -y --no-install-recommends \
        gcc \
        libpq-dev \
        && rm -rf /var/lib/apt/lists/*
    
    # Copiar e instalar requirements antes do código (melhor cache)
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    # Copiar código da aplicação
    COPY . .
    
    # Usuário não-root
    RUN useradd -m appuser && chown -R appuser:appuser /app
    USER appuser
    
    EXPOSE 8000
    
    # Gunicorn com workers Uvicorn (produção)
    CMD ["gunicorn", "main:app", \
         "--worker-class", "uvicorn.workers.UvicornWorker", \
         "--workers", "4", \
         "--bind", "0.0.0.0:8000", \
         "--access-logfile", "-", \
         "--error-logfile", "-"]
    Dica
    workers = (2 × vCPUs) + 1 é a fórmula recomendada para Gunicorn. Em VPS de 2 vCPUs: 5 workers. Em VPS de 1 vCPU: 3 workers. Mais workers não significa mais performance — causa context switching.

    Estrutura da aplicação FastAPI

    Estrutura mínima recomendada para uma API FastAPI pronta para produção:

    python
    # requirements.txt
    fastapi==0.115.0
    uvicorn[standard]==0.32.0
    gunicorn==23.0.0
    sqlalchemy==2.0.36
    asyncpg==0.30.0       # driver async para PostgreSQL
    alembic==1.14.0       # migrações de banco
    python-dotenv==1.0.1
    pydantic-settings==2.6.0
    
    # main.py
    from fastapi import FastAPI
    from contextlib import asynccontextmanager
    from app.database import engine
    from app.routers import users, items
    
    @asynccontextmanager
    async def lifespan(app: FastAPI):
        # Startup
        print("Aplicação iniciando...")
        yield
        # Shutdown
        await engine.dispose()
        print("Aplicação encerrando...")
    
    app = FastAPI(
        title="Minha API",
        version="1.0.0",
        lifespan=lifespan,
        # Desabilitar docs em produção (opcional)
        docs_url=None if os.getenv("ENV") == "production" else "/docs",
    )
    
    app.include_router(users.router, prefix="/users", tags=["users"])
    app.include_router(items.router, prefix="/items", tags=["items"])
    
    @app.get("/health")
    async def health():
        return {"status": "ok"}

    docker-compose.yml completo

    Stack com FastAPI, PostgreSQL e Nginx:

    yaml
    services:
      api:
        build: .
        restart: always
        env_file: .env
        environment:
          DATABASE_URL: postgresql+asyncpg://${DB_USER}:${DB_PASS}@postgres:5432/${DB_NAME}
        depends_on:
          postgres:
            condition: service_healthy
        healthcheck:
          test: ["CMD", "python", "-c",
                 "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
          interval: 30s
          timeout: 10s
          retries: 3
        networks:
          - app_net
    
      postgres:
        image: postgres:16-alpine
        restart: always
        environment:
          POSTGRES_USER: ${DB_USER}
          POSTGRES_PASSWORD: ${DB_PASS}
          POSTGRES_DB: ${DB_NAME}
        volumes:
          - pg_data:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
          interval: 10s
          retries: 5
        networks:
          - app_net
    
      nginx:
        image: nginx:alpine
        restart: always
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
          - certbot_conf:/etc/letsencrypt
          - certbot_www:/var/www/certbot
        depends_on:
          - api
        networks:
          - app_net
    
    volumes:
      pg_data:
      certbot_conf:
      certbot_www:
    
    networks:
      app_net:

    Migrações com Alembic

    Alembic gerencia migrações de banco de dados para SQLAlchemy. Configure e execute no deploy:

    bash
    # Inicializar Alembic (uma vez)
    alembic init migrations
    
    # alembic.ini — definir URL do banco
    # sqlalchemy.url = %(DATABASE_URL)s
    
    # migrations/env.py — importar models para detectar mudanças
    from app.models import Base
    target_metadata = Base.metadata
    
    # Gerar migração a partir dos models
    alembic revision --autogenerate -m "create users table"
    
    # Aplicar migrações pendentes
    alembic upgrade head
    
    # No deploy (via docker-compose exec):
    docker compose exec api alembic upgrade head
    
    # Ver histórico de migrações
    docker compose exec api alembic history
    
    # Rollback de uma migração
    docker compose exec api alembic downgrade -1
    Dica
    Adicione alembic upgrade head ao script de deploy antes de reiniciar os containers da API — garante que o banco está atualizado antes da nova versão do código subir.

    Variáveis de ambiente com Pydantic Settings

    Pydantic Settings valida e tipifica todas as variáveis de ambiente — erros de configuração são detectados no startup, não em runtime:

    python
    # app/config.py
    from pydantic_settings import BaseSettings
    
    class Settings(BaseSettings):
        database_url: str
        secret_key: str
        debug: bool = False
        allowed_origins: list[str] = ["https://seudominio.com.br"]
        max_connections: int = 10
    
        class Config:
            env_file = ".env"
            env_file_encoding = "utf-8"
    
    settings = Settings()
    
    # .env (não commitado no git)
    DATABASE_URL=postgresql+asyncpg://user:pass@postgres:5432/db
    SECRET_KEY=sua_chave_secreta_longa
    DEBUG=false
    ALLOWED_ORIGINS=["https://api.seudominio.com.br"]

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes