green snake
Photo by Pixabay on Pexels.com

Complete Guide to Deploying FastAPI in Production: Reliable Operations with Uvicorn Multi-Workers, Docker, and a Reverse Proxy


Goal and Big Picture

This guide takes you one step beyond development mode (uvicorn app.main:app --reload) and organizes the “path you inevitably walk” when you expose a FastAPI app to the outside world:

  • What to care about in production
  • Building an ASGI server setup using Uvicorn multi-workers
  • Deploying with Docker
  • Putting Nginx (or similar) in front as a reverse proxy (HTTPS, headers, etc.)

FastAPI’s official docs also highlight production concepts like HTTPS, startup method, restarts, replication (process count), and memory. Here we’ll translate those ideas into concrete settings and examples.


Who Benefits (Concrete Personas)

Solo developers / learners (VPS or small cloud)

  • You built a FastAPI app, but you’re not sure what “production startup” should look like.
  • You want stable public access for yourself or a small number of users.
  • You’ve touched Docker, but want the “FastAPI + Docker” shape.

→ You’ll get a minimal, practical setup with Uvicorn multi-workers + Docker, including commands and Dockerfiles.

Small-team backend engineers

  • Your team chose FastAPI and you want a repeatable production deployment flow.
  • You’re considering a reverse proxy (Nginx/Traefik) in front of one or more servers.
  • You want a clear mental model for reload/restart, process count, health checks, and ops.

→ You’ll see the separation of responsibilities between Uvicorn, proxy, and orchestration.

SaaS teams / startups (future scaling)

  • You expect to scale later with Kubernetes or managed container services.
  • You want to start simple (single container) while keeping “replication later” in mind.
  • You’d rather avoid complex Gunicorn setups unless necessary.

→ Modern Uvicorn includes built-in multi-process execution, so you can keep the base architecture simple and scale later.


Accessibility Notes (Readability & Understanding)

  • Structure: “overview → Uvicorn production → Docker → reverse proxy → ops/monitoring → roadmap”
  • Terms: ASGI server / worker / reverse proxy are explained briefly at first use and then reused consistently
  • Code: Configs are split into small blocks with minimal comments to reduce cognitive load
  • Assumed reader: You’ve finished the FastAPI tutorial once, but each section can be read independently

1. Production Deployment Concepts (What to Decide)

Before commands, clarify what you’re optimizing for.

1.1 Key points to consider

  • HTTPS

    • Encrypt client communication.
    • Often handled by a reverse proxy like Nginx/Traefik with certificate automation.
  • Startup & restart

    • The app should come back after server reboot.
    • If the process dies, something should restart it (systemd / container orchestration).
  • Replication (process count)

    • Use multiple processes to utilize CPU cores.
    • Either via Uvicorn --workers or via “more containers/pods” at the cluster level.
  • Memory & resources

    • Each worker consumes memory; the number of workers is bounded by RAM.
    • Decide how many processes you can afford per host.

A good mental model is three layers:

FastAPI app (Uvicorn) / Reverse proxy (Nginx, etc.) / OS or Orchestrator

This keeps roles clear and prevents “mystery configs.”


2. Starting Uvicorn for Production: Multi-Workers and Options

In the past, “Gunicorn + UvicornWorker” was common. But recent Uvicorn versions (often cited around 0.30+) include a built-in multi-process supervisor, so --workers can be enough.

2.1 Minimal production command

Common development command:

uvicorn app.main:app --reload

A simple production-leaning example:

uvicorn app.main:app \
  --host 0.0.0.0 \
  --port 8000 \
  --workers 4

Key differences:

  • Remove --reload (file watching is unnecessary and risky in production)
  • Use --host 0.0.0.0 so external clients (or containers) can reach it
  • Set --workers based on CPU cores (often “cores” to “2× cores” as a starting point)

2.2 Additional useful options

  • --limit-concurrency

    • Caps concurrent requests to prevent runaway CPU/memory usage.
  • --limit-max-requests

    • Recycles workers after a number of requests to mitigate memory leaks over time.
  • --proxy-headers

    • Needed behind a reverse proxy so the app understands client IP and original scheme.

Example:

uvicorn app.main:app \
  --host 0.0.0.0 \
  --port 8000 \
  --workers 4 \
  --proxy-headers \
  --limit-max-requests 10000

3. Containerizing FastAPI with Docker

Many production deployments run FastAPI inside Docker. FastAPI docs also provide Docker templates (“FastAPI in Containers – Docker”).

3.1 A minimal Dockerfile (FastAPI CLI style)

A simple example inspired by the docs, using FastAPI CLI:

FROM python:3.11-slim

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["fastapi", "run", "app/main.py", "--port", "80"]

Example requirements.txt:

fastapi[standard]>=0.113.0,<0.114.0
uvicorn>=0.30.0,<0.31.0
sqlalchemy>=2.0.0,<3.0.0

Build & run:

docker build -t my-fastapi-app .
docker run -d --name my-fastapi -p 8000:80 my-fastapi-app

Now you can access the app at http://localhost:8000.

3.2 Switching the container to start Uvicorn directly

You can also start Uvicorn as the container command:

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"]

A common operational principle is “one container = one process,” while scaling is done by running multiple containers. In practice, many teams still run a few workers inside a container via --workers, and scale containers/pods later with Kubernetes/ECS as needed.


4. Pairing with a Reverse Proxy (Nginx / Traefik)

In many deployments, FastAPI (Uvicorn) runs as an HTTP backend, with Nginx/Traefik in front to handle HTTPS termination and routing.

4.1 Why put a reverse proxy in front?

  • HTTPS certificate management

    • Centralize Let’s Encrypt renewals and TLS settings.
  • Static file delivery

    • Serve images/CSS/JS efficiently via Nginx.
  • Load balancing

    • Distribute traffic across multiple FastAPI containers/hosts.
  • Security and limits

    • IP allowlists, rate limits, basic auth, etc. at the edge.

4.2 Simple Nginx config (single host)

Assuming Uvicorn listens on 127.0.0.1:8000:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }
}

Run Uvicorn with --proxy-headers so it interprets these forwarded headers properly.

4.3 Nginx + FastAPI in containers

With Docker Compose, you usually run Nginx and FastAPI as separate containers. Nginx can forward to http://fastapi:8000 using Docker’s service name resolution.


5. Environment Variables and Configuration Management

Production setups live or die by configuration hygiene.

5.1 Things that vary by environment

  • Database URL
  • API keys/secrets
  • Debug flag
  • Log level

Keep them out of code—load from environment variables.

5.2 Using pydantic-settings

# app/core/settings.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "My FastAPI App"
    environment: str = "dev"
    database_url: str
    secret_key: str
    log_level: str = "info"

    class Config:
        env_file = ".env"

settings = Settings()

Typical pattern:

  • Production: real environment variables (no .env file in the image)
  • Development: .env for convenience

Example container run:

docker run -d \
  -e ENVIRONMENT=prod \
  -e DATABASE_URL=postgresql+psycopg://... \
  -e SECRET_KEY=... \
  -p 8000:80 my-fastapi-app

6. Operations: Monitoring and Restart Strategy

After “how to start it,” the next question is “what happens when it breaks?”

6.1 Running directly on a VPS: systemd

If you run Uvicorn on the host (no Docker), systemd is a common approach:

[Unit]
Description=My FastAPI application
After=network.target

[Service]
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/local/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 --proxy-headers
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Enable and start:

systemctl enable my-fastapi
systemctl start my-fastapi

6.2 Orchestrators: restart, scaling, health checks

In Docker Compose / Kubernetes / ECS, you configure:

  • Auto-restart on failure
  • Replica count
  • Health checks (e.g., /health endpoint)

A simple health check endpoint:

# app/api/health.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/health", tags=["health"])
def health_check():
    return {"status": "ok"}

7. Mini Reference Setup: Docker + Uvicorn + Nginx

A small example that combines everything.

7.1 Directory layout

project/
  app/
    __init__.py
    main.py
    api/
      health.py
      ...
  requirements.txt
  docker-compose.yml
  Dockerfile
  nginx/
    default.conf

7.2 Dockerfile (FastAPI container)

FROM python:3.11-slim

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4", "--proxy-headers"]

7.3 Nginx config

server {
    listen 80;
    server_name _;

    location / {
        proxy_pass         http://fastapi:8000;
        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;
    }
}

7.4 docker-compose.yml

version: "3.9"

services:
  fastapi:
    build: .
    container_name: fastapi-app
    environment:
      - ENVIRONMENT=prod
      - DATABASE_URL=sqlite:///./app.db
    expose:
      - "8000"

  nginx:
    image: nginx:1.27
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - fastapi

Run:

docker-compose up -d --build

Traffic flow:

  • http://localhost/ → Nginx
  • Nginx → fastapi:8000
  • FastAPI(Uvicorn) handles the request

8. Common Pitfalls and Fixes

Symptom Likely cause Fix
Client IP always shows as 127.0.0.1 Proxy headers aren’t interpreted Ensure Nginx sets headers and start Uvicorn with --proxy-headers
Mixed HTTP/HTTPS links Proxy and backend disagree about scheme Set X-Forwarded-Proto in proxy and ensure the app respects it
Errors only after deploy Env vars or DB settings differ Centralize config (Settings + ENV) and run locally in a “prod-like” mode
Crashes under load Too many/few workers, RAM pressure Watch CPU/RAM/latency metrics; tune --workers and/or replicas gradually
“Which architecture is correct?” Too many options Start with the minimal shape: Uvicorn multi-workers + Docker + simple reverse proxy

9. A Gradual Roadmap (Move Toward Production Step by Step)

  1. Local development

    • Use --reload, build features, add tests/logging.
  2. Try production-like Uvicorn locally

    • Remove --reload, test --workers and --limit-max-requests.
  3. Containerize with Docker

    • Build a reproducible image, try Compose with DB if needed.
  4. Add a reverse proxy

    • Start with HTTP routing; add HTTPS termination and static files later.
  5. Deploy to production

    • VPS or cloud (ECS/GKE/etc.), add health checks, logs, metrics, load testing.
  6. Scale later

    • Move to Kubernetes when needed, keeping “simple container, scale at the platform layer” as the guiding principle.

Summary

  • Production deployment is mostly about HTTPS, startup/restart behavior, replication, and memory.
  • Modern Uvicorn supports multi-worker operation via --workers, enabling a simpler “no Gunicorn required” baseline in many cases.
  • Docker improves reproducibility and reduces environment drift; a reverse proxy (Nginx/Traefik) makes it easier to handle HTTPS, routing, static files, and edge-level security.
  • You don’t need to start with a perfect architecture. Begin with the minimal stable shape—Uvicorn multi-workers + Docker—and add proxy/orchestration as your needs grow.

By greeden

Leave a Reply

Your email address will not be published. Required fields are marked *

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)