FastAPI in Production
FastAPI is production-ready but needs proper configuration — the right ASGI server, process management, and infrastructure setup.
ASGI Server Options
Uvicorn (Development)
uvicorn main:app --reload --host 0.0.0.0 --port 8000
Gunicorn + Uvicorn Workers (Production)
pip install gunicorn uvicorn[standard]
gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --timeout 30 --keep-alive 5 --log-level info
Worker formula: (2 × CPU cores) + 1
Application Structure
fastapi-app/
├── app/
│ ├── __init__.py
│ ├── main.py # App factory
│ ├── config.py # Settings (pydantic-settings)
│ ├── database.py # DB connection
│ ├── dependencies.py # Shared deps
│ ├── models/
│ ├── schemas/
│ ├── routers/
│ └── services/
├── tests/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── gunicorn.conf.py
Configuration with pydantic-settings
# app/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
app_name: str = "My API"
debug: bool = False
database_url: str
redis_url: str = "redis://localhost:6379"
secret_key: str
allowed_hosts: list[str] = ["*"]
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
@lru_cache()
def get_settings() -> Settings:
return Settings()
App Factory Pattern
# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from contextlib import asynccontextmanager
from app.config import get_settings
from app.database import init_db
from app.routers import users, products, orders
settings = get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
await init_db()
yield
# Shutdown
# cleanup connections
def create_app() -> FastAPI:
app = FastAPI(
title=settings.app_name,
debug=settings.debug,
lifespan=lifespan,
docs_url="/docs" if settings.debug else None,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_hosts,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["*"])
app.include_router(users.router, prefix="/api/v1")
app.include_router(products.router, prefix="/api/v1")
return app
app = create_app()
Dockerfile (Multi-Stage)
# Build stage
FROM python:3.12-slim as builder
WORKDIR /build
RUN pip install poetry
COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
# Production stage
FROM python:3.12-slim
WORKDIR /app
# Security: non-root user
RUN addgroup --system app && adduser --system --group app
# Install deps
COPY --from=builder /build/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app ./app ./app
USER app
EXPOSE 8000
CMD ["gunicorn", "app.main:app", "--workers", "4",
"--worker-class", "uvicorn.workers.UvicornWorker",
"--bind", "0.0.0.0:8000"]
Nginx Configuration
upstream fastapi_backend {
server app:8000;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
client_max_body_size 10M;
location / {
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
}
location /health {
proxy_pass http://fastapi_backend/health;
access_log off;
}
}
Health Check Endpoint
from fastapi import APIRouter
from sqlalchemy import text
from app.database import get_db
router = APIRouter()
@router.get("/health")
async def health_check(db=Depends(get_db)):
try:
await db.execute(text("SELECT 1"))
db_status = "healthy"
except Exception:
db_status = "unhealthy"
return {
"status": "healthy" if db_status == "healthy" else "degraded",
"database": db_status,
"version": "1.0.0",
}
Gunicorn Config File
# gunicorn.conf.py
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
timeout = 30
keepalive = 5
preload_app = True
accesslog = "-"
errorlog = "-"
loglevel = "info"
Zero-Downtime Deployment
# Rolling update with Docker Swarm
docker service update --image myapp:v2 --update-delay 10s --update-parallelism 1 myapp
# Or with Kubernetes
kubectl set image deployment/fastapi-app app=myapp:v2
kubectl rollout status deployment/fastapi-app
Performance Tips
- Enable response compression with GZipMiddleware
- Use async database drivers (asyncpg, motor)
- Add Redis caching for expensive queries
- Profile with py-spy or pyinstrument
- Set proper connection pool sizes