Web Server and Application Server - jbrucker/home-log GitHub Wiki
The FastAPI back-end service uses asynchronous code and requires an ASGI-capable application server.
In addition, the application as a whole requires a separate (front facing) web server to provide access to static files, TLS, load balancing, and other features needed by scalable web apps.
Purpose and Role
| Component | Primary Role | Use with FastAPI |
|---|---|---|
| Gunicorn with Uvicorn workers | Python application server | Runs the FastAPI app (ASGI). |
| Uvicorn | ||
| Nginx | Reverse proxy, static file server, TLS termination | Fronts the application server. |
Nginx is not an ASGI server, so it cannot run FastAPI directly.
Design
In the simplest practical deployment, we can run Nginx in front of Uvicorn directly:
flowchart LR
A((Client)) -->|Http Req| N[Nginx]
N --static file--> A
N -->|/api| U[Uvicorn]
U -->P([REST App])
P <-->D@{shape: cyl, label: "Database"}
For better CPU utilization and concurrency, we can add Gunicorn as dispatcher for Uvicorn worker threads:
flowchart LR
A((Client)) -->|Http Req| N[Nginx]
N --static file--> A
N -->|/api| G[Gunicorn]
G -->U1[Uvicorn worker](/jbrucker/home-log/wiki/Uvicorn-worker)
G -->U2[Uvicorn worker](/jbrucker/home-log/wiki/Uvicorn-worker)
U1 -->P([REST App])
U2 --> P([REST App])
P <-->D@{shape: cyl, label: "Database"}
Uvicorn (ASGI Server)
Strengths:
- Native ASGI support for async FastAPI
- Direct Python process execution
- Optimized for async frameworks
- WebSocket support
- Easy deployment with workers
Weaknesses:
- Limited static file handling
- Basic load balancing only
- No native TLS termination
- Less robust security
Gunicorn
Strengths
- Native support for ASGI when using
uvicorn.workers.UvicornWorker. - Designed to manage multiple worker processes.
- Handles worker lifecycle, graceful reloads, and resource limits.
- Good performance under high concurrency with proper worker tuning.
Weaknesses
- Not a full-featured reverse proxy.
- No native ability to serve static files efficiently.
- TLS termination must be handled by another component, e.g., Nginx, Traefik, Caddy.
Nginx
Strengths
- Excellent reverse proxy and load balancer.
- Efficient static file service.
- Handles TLS, connection buffering.
- Rate limiting, security headers.
- Offloads slow clients from the application server.
- Very stable under heavy network load.
- Performs caching & compression
Weaknesses
- Cannot run Python application directly (no ASGI support).
- Not a Python process manager.
- Adds configuration overhead if the environment is simple.
- Provides no insight into Python worker management or graceful reloads.
Design Decision
Development: Use Nginx with Uvicorn directly. Measure the performance under simulated load.
Deployment: Add Gunicorn with Uvicorn worker threads if there is a measurable improvement in performance.
Example docker-compose snippet
services:
app:
build: ./backend
# Using 'command:' in docker-compose *overrides* the CMD: in the Dockerfile
# So, this command is in the Dockerfile in the actual code
command: uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
volumes:
- ./backend/app:/app/app
- /tmp:/tmp
ports:
- "8000:8000"
environment:
... (define env vars)
nginx:
image: nginx:alpine
# Map standard HTTP/S ports to high number ports during development
ports:
- "8080:80"
- "8443:443"
depends_on:
- app