Deployment Basics
10 examples to get you started with Containers & Deploy - 7 basic and 3 intermediate.
Prerequisites
uv venv && source .venv/bin/activate
uv pip install "fastapi>=0.115" "uvicorn[standard]>=0.34" "gunicorn>=23.0" "flask>=3.1"- Python 3.14.0 with FastAPI 0.115+, Flask 3.1, and server packages installed.
Basic Examples
1. Minimal WSGI App (Flask)
Flask exposes a WSGI callable consumed by Gunicorn.
from flask import Flask
app = Flask(__name__)
@app.get("/health")
def health():
return {"status": "ok"}gunicorn -w 2 -b 0.0.0.0:8000 'app:app'app:appmeans moduleapp, variableapp- Multiple
-wworkers handle concurrent requests (sync) - Health route is required for load balancers and Kubernetes
Related: Gunicorn & Uvicorn - worker tuning
2. Minimal ASGI App (FastAPI)
FastAPI apps are ASGI - run with Uvicorn or Gunicorn+Uvicorn worker.
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}uvicorn app:app --host 0.0.0.0 --port 8000- ASGI supports async I/O and websockets
- Uvicorn is the reference ASGI server for development and production
- Bind
0.0.0.0in containers, not127.0.0.1
Related: Gunicorn & Uvicorn - production ASGI
3. Environment-Driven Port
Read port from environment for 12-factor deploys.
import os
PORT = int(os.environ.get("PORT", "8000"))- Platforms (Heroku, Fly, ECS) inject
PORT - Default helps local
uvicornwithout env - Validate
int()and fail fast on invalid values
Related: Configuration & Secrets in Prod - env config
4. Process Model Mental Map
Understand who accepts TCP connections.
Client -> Load Balancer -> Gunicorn master -> worker processes -> your app
- Master manages workers; workers run Python app code
- One container often runs one Gunicorn master with N workers
- Long CPU work may need separate queue worker process
Related: Dockerizing Python - container entrypoint
5. Dev vs Prod Server
Never use Flask/FastAPI built-in dev servers in production.
# dev only
flask --app app run --debug
# prod
gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app:app- Dev servers lack robust worker model and security hardening
--reloadis for local iteration only- Production uses Gunicorn/Uvicorn with logging and graceful shutdown
Related: Deployment Best Practices - prod checklist
6. Graceful Shutdown Hook
Drain in-flight requests on SIGTERM from orchestrators.
import signal
def handle_sigterm(signum, frame):
print("shutting down gracefully")
signal.signal(signal.SIGTERM, handle_sigterm)- Kubernetes sends SIGTERM before pod deletion
- Gunicorn/Uvicorn handle graceful timeout - tune
--graceful-timeout - Close DB pools in shutdown events (FastAPI lifespan)
Related: Zero-Downtime Deploys - rolling updates
7. Static Files vs App Server
Serve static assets from CDN or reverse proxy, not Python workers.
location /static/ { alias /var/www/static/; }
location / { proxy_pass http://app:8000; }- Python workers are for dynamic requests
- Whitenoise acceptable for small Django static on PaaS
- Offload TLS termination to load balancer or nginx
Related: Kubernetes for Python Apps - ingress
Intermediate Examples
8. FastAPI Lifespan for Startup/Shutdown
Manage connections with ASGI lifespan context.
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# startup
yield
# shutdown cleanup
app = FastAPI(lifespan=lifespan)- Replace deprecated
@app.on_eventwith lifespan - Initialize DB pool on startup, close on shutdown
- Tests can override lifespan for isolation
Related: Health & Readiness - probe design
9. Multi-Worker Memory Reality
Each worker loads a full copy of your app and models.
gunicorn -w 4 app:app
# 4x import cost, 4x ML model RAM if loaded at import- Scale workers until CPU or RAM saturates - then scale replicas
- Lazy-load heavy models per worker on first request or use shared inference service
- Monitor RSS per container in production
Related: Performance Monitoring (APM) - prod tuning
10. Container Entrypoint Command
Document the exact prod command in Dockerfile CMD.
CMD ["gunicorn", "-w", "2", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "app.main:app"]- Exec form (JSON array) avoids shell signal issues
- Match command in Kubernetes manifest and docker-compose
- CI smoke test runs same entrypoint against staging image
Related: Dockerizing Python - image layout
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.