Claude-skill-registry docker-fastapi

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.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/docker-fastapi" ~/.claude/skills/majiayu000-claude-skill-registry-docker-fastapi && rm -rf "$T"
manifest: skills/data/docker-fastapi/SKILL.md
source content

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:

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:

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:

# 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)

# 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)

# 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):

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:

# 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:

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

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:

# 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:

# 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:

# ❌ 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=""
# 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:

# 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

ApproachTypical 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

# 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

# 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:

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 <container>
  • 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 <container> 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