--- name: docker-fastapi description: Comprehensive Docker containerization for Python/FastAPI applications from development to production deployments. Use when Claude needs to containerize Python/FastAPI applications with proper multi-stage builds, production-ready configurations, security best practices, and optimized Docker images for deployment to cloud platforms or container orchestration systems. --- # Docker FastAPI ## Overview This skill provides comprehensive guidance for containerizing Python/FastAPI applications using Docker. It includes best practices for Dockerfile creation, multi-stage builds, production configurations, security hardening, and deployment strategies from development to production environments. ## Decision Hierarchy When optimizing containers, resolve conflicts in this order: 1. **Security** (non-negotiable): Non-root user (UID 10001+), no secrets in image, health checks 2. **Runtime Size** (beats build speed): Multi-stage builds even if slower to build 3. **Operational Clarity** (beats cleverness): Explicit over implicit, predictable behavior 4. **Build Speed** (when above are satisfied): UV package manager, layer caching ## Core Capabilities ### 1. Dockerfile Generation - **Development Dockerfiles**: Fast feedback loops with hot-reloading - **Production Dockerfiles**: Optimized, secure, minimal images using multi-stage builds - **Security-focused**: Non-root users (UID 10001+), minimal base images, no shell access - **Performance-optimized**: Layer caching, UV official images, dependency optimization - **Best Practice Compliant**: Proper instruction ordering, comprehensive comments, .dockerignore generation ### 2. Multi-stage Build Patterns (P1: Always Use Multi-Stage) - **Builder Stage**: Has compilers, dev tools, UV package manager - **Runtime Stage**: Minimal production image, no build tools, no UV - **Layer Optimization**: Efficient caching and reduced image sizes ### 3. Production Deployment Patterns - **Process Management**: Proper process managers (gunicorn, uvicorn) for FastAPI - **Resource Management**: Memory and CPU constraints - **Health Checks**: Native Python health checks (no curl dependency) - **Environment Configuration**: Proper environment variable handling ### 4. Build Principles **P1: Multi-Stage Always** - Even if current deps don't require compilation, future deps might. **P2: Layer Order Matters** - Copy dependency files first, install, then copy source: ```dockerfile COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-cache COPY src/ ./src/ ``` **P3: Single RUN for Related Operations** - Combine related commands to reduce layers: ```dockerfile RUN groupadd -g 10001 appgroup && \ useradd -u 10001 -g appgroup -s /sbin/nologin appuser && \ chown -R appuser:appgroup /app ``` ## Quick Start Guide ### Production Multi-stage Build (Recommended) For production deployments with optimized security and size: ```dockerfile # syntax=docker/dockerfile:1 # ============================================ # BUILD STAGE - Has UV, compilers, dev tools # ============================================ FROM ghcr.io/astral-sh/uv:python3.12-slim AS builder WORKDIR /app # P2: Dependency files first (changes less frequently) COPY pyproject.toml uv.lock ./ # P3: Install deps into virtual env, not system Python RUN uv sync --frozen --no-cache --no-dev # P2: Source code last (changes most frequently) COPY src/ ./src/ COPY main.py ./ # ============================================ # RUNTIME STAGE - Minimal, no build tools # ============================================ FROM python:3.12-slim AS runtime WORKDIR /app # Runtime environment ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PATH="/app/.venv/bin:$PATH" # P3: User setup in single layer with secure defaults # UID 10001+ for Kubernetes pod security compliance RUN groupadd -g 10001 appgroup && \ useradd -u 10001 -g appgroup -s /sbin/nologin appuser && \ mkdir -p /app && \ chown -R appuser:appgroup /app # P1: Copy only runtime artifacts from builder (no UV, no build tools) COPY --from=builder --chown=appuser:appgroup /app/.venv /app/.venv COPY --from=builder --chown=appuser:appgroup /app/src /app/src COPY --from=builder --chown=appuser:appgroup /app/main.py /app/ # Switch to non-root user (use numeric UID for portability) USER 10001 EXPOSE 8000 # Health check using Python (no curl needed - smaller attack surface) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 # Production command CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` ### With Gunicorn Workers (High Traffic) ```dockerfile # syntax=docker/dockerfile:1 # ============================================ # BUILD STAGE # ============================================ FROM ghcr.io/astral-sh/uv:python3.12-slim AS builder WORKDIR /app COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-cache --no-dev COPY src/ ./src/ COPY main.py ./ # ============================================ # RUNTIME STAGE # ============================================ FROM python:3.12-slim AS runtime WORKDIR /app ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PATH="/app/.venv/bin:$PATH" \ WORKERS=4 \ TIMEOUT=120 \ KEEP_ALIVE=5 \ MAX_REQUESTS=1000 \ MAX_REQUESTS_JITTER=100 RUN groupadd -g 10001 appgroup && \ useradd -u 10001 -g appgroup -s /sbin/nologin appuser && \ mkdir -p /app && \ chown -R appuser:appgroup /app COPY --from=builder --chown=appuser:appgroup /app/.venv /app/.venv COPY --from=builder --chown=appuser:appgroup /app/src /app/src COPY --from=builder --chown=appuser:appgroup /app/main.py /app/ USER 10001 EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 CMD ["sh", "-c", "gunicorn main:app --workers ${WORKERS} --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --timeout ${TIMEOUT} --keep-alive ${KEEP_ALIVE} --max-requests ${MAX_REQUESTS} --max-requests-jitter ${MAX_REQUESTS_JITTER}"] ``` ### Alpine Variant (Smallest Size) ```dockerfile # syntax=docker/dockerfile:1 # ============================================ # BUILD STAGE # ============================================ FROM ghcr.io/astral-sh/uv:python3.12-alpine AS builder WORKDIR /app COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-cache --no-dev COPY src/ ./src/ COPY main.py ./ # ============================================ # RUNTIME STAGE # ============================================ FROM python:3.12-alpine AS runtime WORKDIR /app ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PATH="/app/.venv/bin:$PATH" # Alpine uses addgroup/adduser with different flags RUN addgroup -g 10001 appgroup && \ adduser -D -u 10001 -G appgroup -s /sbin/nologin appuser && \ mkdir -p /app && \ chown -R appuser:appgroup /app COPY --from=builder --chown=appuser:appgroup /app/.venv /app/.venv COPY --from=builder --chown=appuser:appgroup /app/src /app/src COPY --from=builder --chown=appuser:appgroup /app/main.py /app/ USER 10001 EXPOSE 8000 # Alpine has wget built-in (no curl needed) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD wget --spider -q http://localhost:8000/health || exit 1 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` ### Development Dockerfile (Hot-reloading) For development with automatic reloading (security relaxed for convenience): ```dockerfile FROM python:3.12-slim WORKDIR /app # Install UV for fast dependency installation RUN pip install --no-cache-dir uv COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-cache COPY . . ENV PATH="/app/.venv/bin:$PATH" EXPOSE 8000 # Development command with auto-reload CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] ``` ### Legacy: requirements.txt Support If not using pyproject.toml/uv.lock: ```dockerfile # syntax=docker/dockerfile:1 FROM ghcr.io/astral-sh/uv:python3.12-slim AS builder WORKDIR /app COPY requirements.txt ./ RUN uv venv && uv pip install --no-cache -r requirements.txt COPY src/ ./src/ COPY main.py ./ FROM python:3.12-slim AS runtime WORKDIR /app ENV PYTHONUNBUFFERED=1 \ PATH="/app/.venv/bin:$PATH" RUN groupadd -g 10001 appgroup && \ useradd -u 10001 -g appgroup -s /sbin/nologin appuser && \ mkdir -p /app && \ chown -R appuser:appgroup /app COPY --from=builder --chown=appuser:appgroup /app/.venv /app/.venv COPY --from=builder --chown=appuser:appgroup /app/src /app/src COPY --from=builder --chown=appuser:appgroup /app/main.py /app/ USER 10001 EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` ## Production Deployment Patterns ### Docker Compose for Development For local development with database and other services: ```yaml version: '3.8' services: app: build: context: . dockerfile: Dockerfile.dev ports: - "8000:8000" volumes: - .:/app - /app/.venv # Don't mount venv from host environment: - DATABASE_URL=postgresql://user:password@db:5432/mydb depends_on: db: condition: service_healthy db: image: postgres:15-alpine environment: POSTGRES_DB: mydb POSTGRES_USER: user POSTGRES_PASSWORD: password volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d mydb"] interval: 5s timeout: 5s retries: 5 volumes: postgres_data: ``` ### Docker Compose for Production ```yaml version: '3.8' services: app: image: myapp:latest ports: - "8000:8000" environment: - DATABASE_URL=${DATABASE_URL} - API_KEY=${API_KEY} - WORKERS=4 deploy: resources: limits: memory: 512M cpus: '0.5' reservations: memory: 256M cpus: '0.25' restart: unless-stopped healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] interval: 30s timeout: 10s retries: 3 start_period: 5s ``` ## Security Best Practices ### 1. Non-root User Execution (UID 10001+) Always run containers as a non-root user with high UID for K8s compliance: ```dockerfile # Debian/Ubuntu RUN groupadd -g 10001 appgroup && \ useradd -u 10001 -g appgroup -s /sbin/nologin appuser && \ chown -R appuser:appgroup /app USER 10001 # Alpine RUN addgroup -g 10001 appgroup && \ adduser -D -u 10001 -G appgroup -s /sbin/nologin appuser && \ chown -R appuser:appgroup /app USER 10001 ``` **Why UID 10001+?** - Kubernetes `runAsNonRoot` and `MustRunAsNonRoot` policies - Avoids collision with host system users - Better security audit compliance ### 2. Health Checks Without Curl Don't install curl just for health checks - use native tools: ```dockerfile # Python (recommended - no extra dependencies) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 # Alpine (wget is built-in) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD wget --spider -q http://localhost:8000/health || exit 1 ``` ### 3. Secret Management Never hardcode secrets - pass them at runtime: ```dockerfile # ❌ NEVER do this ENV DATABASE_URL=postgresql://user:password@host/db ENV API_KEY=sk_live_abc123 # ✅ Do this - empty defaults, inject at runtime ENV DATABASE_URL="" \ API_KEY="" ``` ```bash # Pass secrets at runtime docker run -e DATABASE_URL=$DATABASE_URL -e API_KEY=$API_KEY myapp:latest # Or use Docker secrets (Swarm) / Kubernetes secrets ``` ### 4. Minimal Attack Surface - Use multi-stage builds (no build tools in production) - Don't install curl, wget, or other tools unless necessary - Use `/sbin/nologin` as shell to prevent interactive access - Use distroless or Alpine for smallest possible image ## Build Optimization ### Layer Caching Strategy Order matters for cache efficiency: ```dockerfile # 1. Base image (rarely changes) FROM python:3.12-slim # 2. System deps (changes occasionally) RUN apt-get update && apt-get install -y ... # 3. Python deps (changes when pyproject.toml changes) COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-cache # 4. Application code (changes frequently) COPY src/ ./src/ ``` ### Image Size Comparison | Approach | Typical Size | |----------|-------------| | `python:3.12` (full) | ~900MB | | `python:3.12-slim` | ~150MB | | `python:3.12-alpine` | ~50MB | | Multi-stage slim | ~100-200MB | | Multi-stage alpine | ~50-100MB | ## Common Commands ### Build and Run ```bash # Build with BuildKit (recommended) DOCKER_BUILDKIT=1 docker build -t my-fastapi-app . # Build with metadata docker build \ --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \ --build-arg VERSION=1.0.0 \ -t my-fastapi-app:1.0.0 . # Run the container docker run -p 8000:8000 my-fastapi-app # Run with environment variables docker run -p 8000:8000 -e WORKERS=8 my-fastapi-app ``` ### Validation Tests ```bash # Test 1: Verify non-root user docker run --rm my-fastapi-app whoami # Expected: appuser (not root) # Test 2: Verify UID docker run --rm my-fastapi-app id # Expected: uid=10001(appuser) gid=10001(appgroup) # Test 3: Verify health check works docker run -d --name test my-fastapi-app sleep 10 docker inspect test | jq '.[0].State.Health.Status' # Expected: "healthy" docker rm -f test # Test 4: Check image size docker images my-fastapi-app ``` ## Kubernetes Deployment For K8s, `HEALTHCHECK` is ignored - use pod probes: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: fastapi-app spec: replicas: 3 selector: matchLabels: app: fastapi-app template: metadata: labels: app: fastapi-app spec: securityContext: runAsNonRoot: true runAsUser: 10001 runAsGroup: 10001 fsGroup: 10001 containers: - name: app image: my-fastapi-app:latest ports: - containerPort: 8000 resources: limits: memory: "512Mi" cpu: "500m" requests: memory: "256Mi" cpu: "250m" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 30 timeoutSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 2 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL ``` ## Troubleshooting ### 1. Application Not Starting - Check logs: `docker logs ` - Verify correct module path in CMD - Ensure all dependencies installed in venv ### 2. Permission Issues - Check file ownership: `docker run --rm myapp ls -la /app` - Ensure directories created before USER directive - Use `--chown` with COPY commands ### 3. Health Check Failing - Test endpoint manually: `docker exec python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"` - Increase `--start-period` for slow-starting apps - Check application logs for errors ### 4. Large Image Size - Use multi-stage builds - Use Alpine base images - Remove `__pycache__` and `.pyc` files - Check for unnecessary dependencies ## References This skill includes the following resources: - **scripts/**: Docker build and deployment helper scripts - **references/**: Detailed Docker and FastAPI deployment guides - **assets/**: Dockerfile templates for different scenarios