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
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", "-"]Estrutura da aplicação FastAPI
Estrutura mínima recomendada para uma API FastAPI pronta para produção:
# 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:
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:
# 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 -1Variá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:
# 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.