Skip to content

FastAPI Deployment and Production

Production FastAPI deployments typically use Docker for containerization, Nginx as reverse proxy with SSL termination, and Gunicorn managing multiple Uvicorn workers. Monitoring with Prometheus/Grafana and structured logging complete the production stack.

Key Facts

  • Production architecture: Client -> Nginx (SSL) -> Gunicorn -> Uvicorn workers -> FastAPI
  • Docker wraps the app with all dependencies into a portable container
  • docker compose orchestrates multiple services (app, DB, Redis, Celery)
  • API versioning via fastapi-versioning for external clients
  • Prometheus collects metrics; Grafana visualizes; Sentry tracks errors
  • Structured JSON logging enables machine-parseable, searchable logs

Patterns

Dockerfile

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
docker build .
docker run -p 1234:8000 <image>  # host:container port mapping

Docker Compose

services:
  db:
    image: postgres:15
    env_file: .env-non-dev
  redis:
    image: redis:7
  booking:
    build: .
    ports:
      - "7777:8000"
    depends_on:
      - db
      - redis
    env_file: .env-non-dev
  celery:
    build: .
    command: celery -A celery_config worker
    depends_on:
      - redis
  flower:
    build: .
    command: celery flower
    ports:
      - "5555:5555"

In Docker Compose, replace localhost with service names (db, redis) for inter-container networking.

Nginx HTTPS Configuration

server {
    listen 80;
    server_name example.ru;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.ru;
    ssl_certificate /etc/nginx/ssl/bundle.crt;
    ssl_certificate_key /etc/nginx/ssl/private.key;

    location / {
        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }
}

SSL certificate bundle: cat domain.crt intermediate.crt root.crt > bundle.crt (order matters).

Request Timing Middleware

@app.middleware("http")
async def add_process_time(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    process_time = time.time() - start
    logger.info("Request", extra={"process_time": process_time, "path": request.url.path})
    return response

API Versioning

from fastapi_versioning import VersionedFastAPI, version

@router.get("/bookings")
@version(1)
async def get_bookings_v1(): ...

@router.post("/bookings")
@version(2)
async def create_booking_v2(): ...
# Docs at /v1/docs, /v2/docs

Production Launch

# Gunicorn with Uvicorn workers
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Log Levels

  • DEBUG - development diagnostics
  • INFO - operational messages (started, user created)
  • WARNING - recoverable issues
  • ERROR - failures requiring attention
  • CRITICAL - system-level failures

Gotchas

  • depends_on only controls startup order, not readiness - app may fail if DB isn't ready yet
  • Mount static files / admin panels AFTER VersionedFastAPI initialization
  • Free tier hosting (Render.com): set workers=1, use BackgroundTasks instead of Celery
  • SSL bundle certificate order matters: domain -> intermediate -> root
  • Sentry integration provides error tracking with stack traces and frequency analytics

See Also