How to Dockerize a FastAPI Application with Uvicorn
Prerequisites
- FastAPI application with a
requirements.txtorpyproject.toml - Docker installed
- Python 3.11+
Step 1: Create the Dockerfile
# Build stage
FROM python:3.12-slim AS builder
WORKDIR /app
# Install build dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Runtime stage
FROM python:3.12-slim
WORKDIR /app
# Copy installed packages from builder
COPY --from=builder /root/.local /root/.local
# Copy application code
COPY . .
# Ensure local bin is in PATH
ENV PATH=/root/.local/bin:$PATH
# Expose FastAPI default port
EXPOSE 8000
# Run Uvicorn
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Why multi-stage? The builder stage installs dependencies with full tooling (gcc, headers). The runtime stage is slim — no compilers, no build tools. This reduces image size significantly (~150 MB → ~80 MB for a typical FastAPI app).
Step 2: Configure Uvicorn properly
Don’t hardcode Uvicorn settings in Dockerfile. Use environment variables instead:
# main.py
import os
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"status": "ok"}
if __name__ == "__main__":
uvicorn.run(
"main:app",
host=os.getenv("HOST", "0.0.0.0"),
port=int(os.getenv("PORT", 8000)),
workers=int(os.getenv("WORKERS", 1)),
reload=os.getenv("RELOAD", "false").lower() == "true"
)
Step 3: Create docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/mydb
- REDIS_URL=redis://redis:6379
- ENVIRONMENT=development
- LOG_LEVEL=debug
volumes:
- .:/app
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
pgdata:
redis_data:
Note depends_on with condition: service_healthy — this waits for PostgreSQL to actually accept connections, not just start the container.
Step 4: Add a health check endpoint
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health_check():
return {"status": "healthy"}
The health check in docker-compose above uses this endpoint.
Step 5: .dockerignore
__pycache__/
*.py[cod]
.env
.venv
venv/
.git
.gitignore
*.md
Dockerfile
docker-compose.yml
.pytest_cache/
.mypy_cache/
Step 6: Environment variables with Pydantic
Use Pydantic for validated config:
# config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str = "postgresql+asyncpg://user:pass@localhost:5432/mydb"
redis_url: str = "redis://localhost:6379"
environment: str = "development"
log_level: str = "info"
class Config:
env_file = ".env"
settings = Settings()
Production hardening
# Use a non-root user
RUN addgroup --system app && adduser --system --group app
USER app
# Set Python optimizations
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PYTHONOPTIMIZE=2
# Use multiple workers (one per CPU core typically)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Verification
docker-compose up --build
# Test endpoint
curl http://localhost:8000/
# {"status":"ok"}
# Test health check
curl http://localhost:8000/health
# {"status":"healthy"}
# Check container health status
docker ps --filter "name=api"
# Should show "(healthy)" in the STATUS column
FAQ
Q: Should I use --workers with Uvicorn?
Yes in production, but use only 1 worker in development (for hot reload). For production, set workers to 2 * CPU_CORES + 1.
Q: Gunicorn or Uvicorn? Uvicorn alone is fine for most deployments. Use Gunicorn + Uvicorn workers if you need process management features like graceful restarts.
Q: How do I handle async database drivers?
Use asyncpg for PostgreSQL and aioredis for Redis. They require psycopg[binary] and redis[hiredis] in requirements.