Skip to content

Chapter 22 · Production Deployment ​

Chapter goals: Understand the core conceptual model of production deployment, master multi-worker tuning, Docker image building best practices, and health checks with graceful shutdown.

22.1 The Conceptual Model of Deployment ​

When deploying FastAPI (or any Web API), there are only six things that truly matter:

  1. Security (HTTPS) — handled by a TLS-terminating proxy outside the application;
  2. Auto-start on boot — app relaunches automatically after server restart;
  3. Crash recovery — process auto-recovers after crashing;
  4. Replication (replicas) — how many processes to run to utilize multiple cores;
  5. Memory — control per-process memory usage;
  6. Pre-start steps — database migrations, etc.

Understand a key layering: the ASGI server (Uvicorn) runs your app, but HTTPS is typically not its job. A TLS-terminating proxy (Traefik / Caddy / Nginx + Certbot / cloud load balancer) decrypts traffic on the outside and forwards HTTP to Uvicorn. "Liveness and restart" is handled by process managers like Docker/Kubernetes/systemd/Supervisor. Each component has its own role — do not ask Python processes to do the proxy's job.

Client --HTTPS--> TLS Terminating Proxy (443) --HTTP--> Uvicorn/FastAPI (8000, internal)

22.2 fastapi run and Multi-Worker Tuning ​

Development uses fastapi dev; the official CLI provides the production counterpart:

bash
# Single process
fastapi run main.py --port 80

# Multi-worker: 1 parent process + 4 worker subprocesses
fastapi run main.py --workers 4

# Equivalent native approach
uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4

# If deployed behind a proxy like Nginx/Traefik, trust proxy headers
fastapi run app/main.py --port 80 --proxy-headers

--workers 4 starts 1 parent process (process manager) + 4 worker processes, each handling requests independently, utilizing multiple CPU cores.

Worker count tuning rules of thumb:

  • CPU-bound primary →接近 CPU core count;
  • I/O-bound primary (typical API app) → higher, e.g. (2 × CPU) + 1;
  • Upper limit is constrained by memory: each worker is a fully独立 process, memory usage scales linearly;
  • Container/Kubernetes exception: usually run 1 Uvicorn process per container, scale horizontally via replica count (pod count) — avoid double-copying that oversells resources.

Do not use fastapi dev in production

The dev mode's auto-reload and debugging features are neither performant nor secure for production; always use fastapi run or bare uvicorn in production.

22.3 Dockerfile Best Practices ​

Official recommended Dockerfile structure (every line has a reason):

dockerfile
FROM python:3.13

WORKDIR /code

# ① Copy dependency list first —— it rarely changes
COPY ./requirements.txt /code/requirements.txt

# ② Install dependencies next —— when the list has not changed, Docker cache is hit, completing in seconds
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

# ③ Copy application code last —— changes most frequently, placed last to keep cache effective
COPY ./app /code/app

# ④ CMD must use exec form (JSON array)
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]

Four key details:

  1. Layer order IS cache strategy: requirements.txt changes rarely — put it first; app/ changes daily — put it last. Wrong order means reinstalling all dependencies for every one-line code change.
  2. --no-cache-dir is a pip option that does not leave downloaded packages in the image, reducing size.
  3. CMD must use exec form (["fastapi", "run", ...]): shell form (CMD fastapi run ...) runs the command in a shell subprocess, which cannot properly receive SIGTERM, breaking graceful shutdown. 4.配套 .dockerignore to prevent junk from entering the image:
text
.git
__pycache__
*.pyc
.env        # Never bake into the image!
docs
tests
.venv

Build and run:

bash
docker build -t myapp .
docker run -d --name mycontainer -p 80:80 myapp

22.4 Health Checks and Graceful Shutdown ​

Uvicorn supports graceful shutdown: when it receives SIGTERM, it finishes in-flight requests before exiting. Configure this in your orchestrator:

yaml
# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "80:80"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    stop_grace_period: 30s   # Wait up to 30s for in-flight requests to finish

Add a /health endpoint:

python
@app.get("/health")
async def health():
    return {"status": "ok"}

22.5 Chapter Summary ​

  • Deployment has six concerns: HTTPS, auto-start, crash recovery, replication, memory, pre-start steps;
  • fastapi run for production; --workers N for multi-process; never use dev mode in production;
  • Dockerfile layer order is a cache strategy: dependencies first, code last;
  • Graceful shutdown: Uvicorn handles SIGTERM; set stop_grace_period in your orchestrator.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Why should you not use fastapi dev in production?

2. Why must CMD use exec form (JSON array) in Docker?

3. In a Dockerfile, why copy requirements.txt before the application code?

4. What does stop_grace_period do in Docker Compose?

🛠️ Hands-On Exercises ​

  1. Write a Dockerfile for your FastAPI app following the official best practices (layer order, exec form CMD, .dockerignore); build and run it.
  2. Add a /health endpoint; configure Docker Compose healthcheck and stop_grace_period; verify health status with docker ps.
  3. Scale to 4 workers with --workers 4; measure RPS improvement vs single worker on a simple endpoint.

Course complete. You now have a production-grade FastAPI application from zero to deployment.