Deployment Best Practices
Reproducible, observable, reversible releases keep Python APIs available while teams ship daily. These rules apply from Dockerfiles through CI/CD to Kubernetes rollouts.
How to Use This List
- Validate before first production deploy and after any SEV involving rollout or config.
- Pair with CI/CD Pipelines for automated enforcement.
- Treat missing readiness endpoints as a merge blocker for HTTP services.
A - Build and Image
- Multi-stage Docker build with slim base image. No compiler toolchain in runtime layer.
- Pin Python 3.14 and deps with uv lockfile.
uv sync --frozenin CI and Dockerfile. - Run container as non-root user. Writable paths only where required.
-
.dockerignoreexcludes.venv, tests, git. Smaller context and faster builds. - Tag images with git SHA, never
:latestin prod. Immutable artifact per release.
B - Runtime and Servers
- Use Gunicorn+UvicornWorker or equivalent for prod FastAPI. Not dev server with
--reload. - Document exact container CMD in repo. Same command in K8s manifest and docker-compose.
- Set CPU/memory requests and limits from load tests. Prevent OOMKill and noisy neighbor scheduling.
- Configure graceful shutdown timeouts. Match
terminationGracePeriodSecondsand Gunicorn--graceful-timeout. - Bind
0.0.0.0behind LB/ingress. TLS terminates at edge, not ad-hoc in app container.
C - Config and Secrets
- Validate settings with Pydantic 2 at startup. Fail fast on missing prod env vars.
- Inject secrets from SSM/Secrets Manager/K8s Secret. Not in image layers or git.
- Separate config per environment with same image. Only env references change between staging/prod.
- Redact secrets in logs and error reports. Use
SecretStrand Sentry scrubbers. - Plan secret rotation without rebuild. Rolling restart picks up new values.
D - Deploy and Migrations
- Rolling update with
maxUnavailable: 0for HA APIs. Surge new pods before draining old. - Readiness checks DB and critical deps; liveness stays cheap. Avoid restart loops on blips.
- Run migrations as separate Job or phase. Expand-contract for breaking schema changes.
- Smoke test
/healthafter deploy in CI/CD. Block promotion on failure. - Document rollback: redeploy previous SHA. Practice rollback in staging quarterly.
E - Observability and Ops
- Emit structured JSON logs to stdout. Container platforms aggregate automatically.
- Expose Prometheus metrics or APM on every service. Error rate and latency alarms per route.
- Track DORA metrics: deploy frequency, lead time, MTTR, change fail rate. Improve deliberately.
- Separate web and worker Deployments. Scale CPU-bound workers independently.
- Scan images in CI for CVEs. Rebuild base image on patch cadence.
FAQs
What is minimum prod setup?
Tested image, Gunicorn/Uvicorn, health endpoints, env-based config, CI test gate, one-click rollback tag.
When use Lambda vs K8s?
Lambda for event/spiky work; K8s for steady HTTP, websockets, and uniform multi-service platform.
How many Gunicorn workers?
Load test - start near CPU count for async I/O services; reduce when memory-bound per worker.
Are .env files ever OK?
Local dev only - never in image; prod uses platform secret injection.
How do I handle static files?
CDN or object storage for scale; collectstatic in build for Django as needed.
What about feature flags?
Decouple risky code deploy from exposure - flags off until rollout verified.
How do I deploy Django admin safely?
Same rolling rules; long-running admin exports may need separate worker or timeout tuning.
Preview environments?
PR-triggered deploy with seeded data accelerates review before staging promotion.
How does docs lint fit?
Run npx tsx scripts/lint-docs.ts in prebuild when shipping doc site with app monorepo.
Biggest deploy anti-pattern?
Shipping breaking DB migration and code change atomically - always expand-contract across releases.
Related
- Deployment Basics - WSGI/ASGI fundamentals
- Zero-Downtime Deploys - rolling and migrations
- CI/CD Pipelines - automation
- Observability Best Practices - signals in prod
- Configuration & Secrets in Prod - 12-factor config
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+.