Deployment Strategies
Learn how to deploy FastAPI applications in various environments to ensure scalability and reliability.
Content
Dockerizing FastAPI Applications
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Dockerizing FastAPI Applications — The No-Nonsense, Slightly Dramatic Guide
"You wrote an async endpoint, tuned workers, and benchmarked like a wizard — now put it in a box and ship it." — Your Future Production Self
You already learned how to run FastAPI with Uvicorn and how to pair Gunicorn with Uvicorn workers. You also know asynchronous programming basics for peak throughput. This guide assumes that and picks up where those topics left off: packaging your app reliably with Docker so your async performance doesn't implode when the server moves from your laptop to the cloud.
Why Dockerize FastAPI? (Quick reminder)
- Portability: the same image runs in CI, staging, production.
- Reproducibility: dependency hell? Not here.
- Isolation: you control Python, system libs, and startup.
Think of Docker as a little habitat for your FastAPI app: it has everything the app needs, so it doesn’t show up to the cloud party naked and confused.
The Core Idea — Minimal Dockerfile, Maximum Speed
Below are two practical Dockerfile approaches: a simple one (for small projects) and a production-ready multi-stage build (recommended).
Minimal (good for dev & prototypes)
FROM python:3.11-slim
WORKDIR /app
COPY pyproject.toml poetry.lock* ./
RUN pip install --no-cache-dir pip pip-tools
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Production-ish (multi-stage, smaller image)
# Builder
FROM python:3.11-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y build-essential gcc \
&& rm -rf /var/lib/apt/lists/*
COPY pyproject.toml poetry.lock* ./
RUN pip install --no-cache-dir poetry
RUN poetry config virtualenvs.create false && poetry install --no-dev --no-interaction --no-ansi
COPY . /app
# Runtime
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /app /app
# create non-root user
RUN useradd -m fastapiuser && chown -R fastapiuser /app
USER fastapiuser
EXPOSE 8000
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-w", "4", "app.main:app", "-b", "0.0.0.0:8000", "--log-level", "info", "--access-logfile", "-"]
Notes:
- Multi-stage reduces final image size and removes build tools.
- Use a non-root user in runtime image for security.
Uvicorn vs Gunicorn in Docker — quick matrix
| Strategy | When to use | Pros | Cons |
|---|---|---|---|
| Uvicorn directly | Small apps, debugging, low ops | Simple, smaller image | No robust process management; spawn multiple containers for concurrency |
| Gunicorn + UvicornWorkers | Production, multi-core servers | Master process controls worker lifecycle, graceful reloads | Slightly more complexity; set worker count correctly |
If you've studied worker counts from the Gunicorn lesson, apply the same rules here: worker_count = (2 x CPU) + 1 is a reasonable starting point — but adjust for CPU-bound tasks vs async I/O.
Docker Compose: Bring Your Full Stack Together
You probably have a DB. Docker Compose makes local integration testing easy.
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
depends_on:
- db
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: postgres
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Healthchecks + depends_on = smoother local dev and clearer CI failures.
Async Considerations Inside Containers
- Don’t block the event loop: CPU-heavy work should be offloaded to worker threads, tasks, or external services.
- Worker count: too many workers can thrash CPU and memory; too few underutilizes your container.
- If you use Gunicorn+UvicornWorker, each worker has its own event loop. So your concurrency scales by workers x async concurrency.
Pro-tip: on small AWS ECS tasks, set CPU/memory and adjust worker_count accordingly. Containers are NOT magical; they still run on real CPUs.
Practical Tips & Best Practices
- Pin your base image and dependencies. Don’t rely on "latest" in production.
- Keep images small. Use slim images and multi-stage builds.
- Use environment variables for secrets. In K8s/ECS, use secrets stores — don’t bake them into images.
- Logging to stdout/stderr. Containers should stream logs to the host logging driver; avoid file-based logs inside the container.
- Expose a /health or /ready endpoint to let orchestrators do graceful rolling updates.
- Set resource limits. Kubernetes/ECS need CPU/memory requests and limits to schedule correctly.
- Add a simple ENTRYPOINT script when you need pre-start checks (migrations, wait-for-db). Keep it idempotent.
Example lightweight entrypoint:
#!/usr/bin/env bash
set -e
# Wait for DB
until psql "$DATABASE_URL" -c '\q' >/dev/null 2>&1; do
echo "Waiting for DB..."
sleep 1
done
exec "$@"
Deployment Options — where to run the image
- Container Services: AWS ECS/Fargate, Google Cloud Run, Azure Container Instances — good if you want managed containers.
- Kubernetes: best for complex orchestration, autoscaling, and rolling updates.
- VM + Docker: simple, but you manage scaling and health yourself.
Choose based on team expertise and operational complexity. If you want to get to market fast, Cloud Run or Fargate with autoscaling is a sane choice.
Quick Checklist Before You Push to Production
- Multi-stage build in place
- Non-root runtime user
- Proper command: Gunicorn+Uvicorn or Uvicorn with process manager
- Health/readiness endpoints
- Logging to stdout/stderr
- Secrets stored outside the image
- Resource limits and worker tuning tested
- CI pipeline builds and scans the image
Final Mic Drop
Dockerizing isn't just "slap a Dockerfile on it." It's about aligning your async app behavior with how containers schedule and allocate CPU and memory. If you married your async knowledge (event loops, non-blocking I/O) to process management (Gunicorn/Uvicorn) and topped it with a tight Docker build, you now have a production-ready, scalable FastAPI service.
"Ship the image, not the chaos." — Me, probably, at 2am while pushing to prod
Happy containerizing. If you want, I can generate a tailored Dockerfile + docker-compose for your project structure (send repo layout) and we can make CI/CD magic next.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!