Deployment: workers, proxies and containers

FastAPI · Advanced · 16 min

Development defaults (one process, auto-reload, localhost) are wrong for production in ways that cost uptime and performance.

In production run the app with fastapi run (or uvicorn main:app --host 0.0.0.0), never with --reload.

Workers. One Uvicorn process uses one CPU core for Python code. fastapi run --workers 4 starts a manager with four worker processes, each with its own event loop, memory, threadpool and lifespan. On a VM, start near one worker per core and measure. On Kubernetes or similar, run a single process per container and scale with replicas instead; the orchestrator already does process management, health checks and restarts. Gunicorn with Uvicorn workers is a common older setup that's no longer necessary.

Behind a proxy. TLS usually terminates at a load balancer or reverse proxy, which forwards plain HTTP. For correct client IPs, https URLs and redirects, the server must trust X-Forwarded-* headers: --proxy-headers (on by default with fastapi run) plus --forwarded-allow-ips listing the proxy's addresses. If the proxy serves the app under a path prefix like /api, set --root-path /api so the docs and generated URLs include it.

Containers. Copy and install requirements before copying your code so Docker caches the dependency layer, and use the exec form of CMD so the server is PID 1 and receives SIGTERM for a graceful shutdown.

Operational basics.

  • Configuration through environment variables (the settings lesson).
  • A cheap /health endpoint for liveness and one that checks dependencies for readiness.
  • Database migrations as a separate deploy step, not inside every worker's startup.
  • Logs to stdout, collected by the platform.

Example

# Dockerfile
FROM python:3.13-slim

WORKDIR /code
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1

# dependencies first: this layer is cached until requirements.txt changes
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY ./app ./app

# exec form: the server is PID 1 and gets SIGTERM for graceful shutdown
# one process per container; scale with replicas (or add --workers on a VM)
# behind a proxy, also pass: "--forwarded-allow-ips", "<proxy IPs>"
CMD ["fastapi", "run", "app/main.py", "--port", "80"]

# docker build -t myapi . && docker run -p 8000:80 --env-file .env myapi

Remember

  • Production: fastapi run / uvicorn without --reload
  • --workers N for VMs; one process per container on Kubernetes
  • Behind a proxy, trust X-Forwarded-* headers only from the proxy and set root_path for prefixes
  • Docker: install deps before copying code, exec-form CMD

Watch out for

  • Trusting forwarded headers from everyone (--forwarded-allow-ips '*') on a server that's directly reachable from the internet.

Check yourself

  1. You deploy to Kubernetes. What's the usual recommendation for Uvicorn workers?
  2. Behind an HTTPS load balancer, your redirects point to http:// URLs. What's missing?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: fastapi.tiangolo.com/deployment/server-workers/