diff --git a/python/Dockerfile.distroless b/python/Dockerfile.distroless new file mode 100644 index 0000000..046f8ca --- /dev/null +++ b/python/Dockerfile.distroless @@ -0,0 +1,73 @@ +# syntax=docker/dockerfile:1.7 +# +# Python Distroless Dockerfile (Maximum Security) +# Features: No shell, no package manager, minimal attack surface +# +# Build args: +# PYTHON_VERSION - Python version (default: 3.12) +# +# Note: Distroless images have no shell - debugging requires ephemeral containers +# +# Usage: +# docker build -t myapp:latest . +# docker run --rm -p 8000:8000 myapp:latest + +# ============================================================================= +# Stage 1: Build environment +# ============================================================================= +ARG PYTHON_VERSION=3.12 + +FROM python:${PYTHON_VERSION}-slim AS builder + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Create virtual environment +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# Install dependencies +COPY requirements.txt . +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install --no-compile -r requirements.txt + +# Copy application +COPY . . +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install --no-compile . + +# ============================================================================= +# Stage 2: Distroless runtime (maximum security) +# ============================================================================= +FROM gcr.io/distroless/python3-debian12 AS runtime + +WORKDIR /app + +# Copy virtual environment +COPY --from=builder /opt/venv /opt/venv + +# Copy application +COPY --from=builder /app /app + +# Set Python path to use venv +ENV PYTHONPATH="/opt/venv/lib/python3.12/site-packages" \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +# Distroless runs as nonroot by default (uid 65532) +USER nonroot + +EXPOSE 8000 + +# No ENTRYPOINT - distroless uses the image's default entrypoint +CMD ["-m", "app.main"] + +# Note: HEALTHCHECK not supported in distroless (no shell) +# Use Kubernetes probes or Docker healthcheck with exec form + +LABEL org.opencontainers.image.title="My Python App (Distroless)" \ + org.opencontainers.image.description="Maximum security Python image" diff --git a/python/Dockerfile.pip b/python/Dockerfile.pip new file mode 100644 index 0000000..2c4b78a --- /dev/null +++ b/python/Dockerfile.pip @@ -0,0 +1,89 @@ +# syntax=docker/dockerfile:1.7 +# +# Python Multi-Stage Dockerfile with pip (Traditional) +# Features: Layer caching, minimal image, non-root, security hardened +# +# Build args: +# PYTHON_VERSION - Python version (default: 3.12) +# +# Usage: +# docker build -t myapp:latest . +# docker run --rm -p 8000:8000 myapp:latest + +# ============================================================================= +# Stage 1: Build environment +# ============================================================================= +ARG PYTHON_VERSION=3.12 + +FROM python:${PYTHON_VERSION}-slim AS builder + +# Install build dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential \ + gcc && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Create virtual environment +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# Install dependencies first (layer caching) +COPY requirements.txt . +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install --no-compile -r requirements.txt + +# Copy and install application +COPY . . +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install --no-compile . + +# ============================================================================= +# Stage 2: Production runtime (minimal) +# ============================================================================= +FROM python:${PYTHON_VERSION}-slim AS runtime + +# Security: Create non-root user +RUN groupadd --gid 1000 appgroup && \ + useradd --uid 1000 --gid 1000 --shell /bin/false --create-home appuser + +# Install runtime dependencies only +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + tini && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +WORKDIR /app + +# Copy virtual environment from builder +COPY --from=builder --chown=appuser:appgroup /opt/venv /opt/venv + +# Copy application code (if not installed as package) +COPY --from=builder --chown=appuser:appgroup /app /app + +# Set environment +ENV PATH="/opt/venv/bin:$PATH" \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONFAULTHANDLER=1 + +# Security: Switch to non-root user +USER appuser + +# Health check +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 + +EXPOSE 8000 + +ENTRYPOINT ["tini", "--"] +CMD ["python", "-m", "app.main"] + +# OCI Labels +LABEL org.opencontainers.image.title="My Python App" \ + org.opencontainers.image.description="Python application" \ + org.opencontainers.image.version="1.0.0" diff --git a/python/Dockerfile.uv b/python/Dockerfile.uv new file mode 100644 index 0000000..3276f95 --- /dev/null +++ b/python/Dockerfile.uv @@ -0,0 +1,103 @@ +# syntax=docker/dockerfile:1.7 +# +# Python Multi-Stage Dockerfile with UV Package Manager +# Features: Fast builds, minimal image, non-root, security hardened +# +# Build args: +# PYTHON_VERSION - Python version (default: 3.12) +# UV_VERSION - UV package manager version (default: 0.5) +# +# Usage: +# docker build -t myapp:latest . +# docker run --rm -p 8000:8000 myapp:latest + +# ============================================================================= +# Stage 1: Build environment with UV +# ============================================================================= +ARG PYTHON_VERSION=3.12 +ARG UV_VERSION=0.5 + +FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv +FROM python:${PYTHON_VERSION}-slim AS builder + +# Install UV from official image +COPY --from=uv /uv /usr/local/bin/uv + +# Set UV environment variables for reproducible builds +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_PYTHON_DOWNLOADS=never + +WORKDIR /app + +# Install dependencies first (layer caching) +# Copy only dependency files to maximize cache hits +COPY pyproject.toml uv.lock* ./ + +# Install dependencies into the virtual environment +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-install-project --no-dev + +# Copy application source +COPY . . + +# Install the project itself +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev + +# ============================================================================= +# Stage 2: Production runtime (minimal) +# ============================================================================= +FROM python:${PYTHON_VERSION}-slim AS runtime + +# Security: Create non-root user +RUN groupadd --gid 1000 appgroup && \ + useradd --uid 1000 --gid 1000 --shell /bin/false --create-home appuser + +# Security: Install only essential runtime packages, remove caches +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + tini && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +WORKDIR /app + +# Copy virtual environment from builder +COPY --from=builder --chown=appuser:appgroup /app/.venv /app/.venv + +# Copy application code +COPY --from=builder --chown=appuser:appgroup /app /app + +# Set environment +ENV PATH="/app/.venv/bin:$PATH" \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONFAULTHANDLER=1 + +# Security: Switch to non-root user +USER appuser + +# Health check (customize endpoint as needed) +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 + +# Expose port (documentation) +EXPOSE 8000 + +# Use tini as init system for proper signal handling +ENTRYPOINT ["tini", "--"] + +# Default command (override in docker-compose or at runtime) +CMD ["python", "-m", "app.main"] + +# ============================================================================= +# OCI Labels (customize these) +# ============================================================================= +LABEL org.opencontainers.image.title="My Python App" \ + org.opencontainers.image.description="Python application with UV" \ + org.opencontainers.image.version="1.0.0" \ + org.opencontainers.image.vendor="Your Organization" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.source="https://github.com/your-org/your-repo" diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..643336c --- /dev/null +++ b/python/README.md @@ -0,0 +1,153 @@ +# Python Docker Templates + +Production-ready Python Dockerfile templates with security best practices. + +## Templates + +| Template | Base Image | Use Case | +|----------|-----------|----------| +| `Dockerfile.uv` | python-slim | **Recommended** - Fast builds with UV package manager | +| `Dockerfile.pip` | python-slim | Traditional pip workflow | +| `Dockerfile.distroless` | distroless/python3 | Maximum security (no shell) | + +## Features + +All templates include: + +- ✅ **Multi-stage builds** - Small final images (typically 50-150MB) +- ✅ **Non-root user** - Never run as root in production +- ✅ **Layer caching** - Dependencies cached separately from code +- ✅ **BuildKit caching** - Pip/UV cache persisted across builds +- ✅ **Tini init** - Proper signal handling and zombie reaping +- ✅ **Health checks** - Built-in health check endpoint +- ✅ **OCI labels** - Standard container metadata + +## Quick Start + +### Using UV (Recommended) + +```bash +# Copy template +cp Dockerfile.uv ../my-project/Dockerfile + +# Ensure you have pyproject.toml and optionally uv.lock +cd ../my-project + +# Build +docker build -t myapp:latest . + +# Run +docker run --rm -p 8000:8000 myapp:latest +``` + +### Using pip + +```bash +cp Dockerfile.pip ../my-project/Dockerfile +# Ensure you have requirements.txt +docker build -t myapp:latest . +``` + +## Customization + +### Change Python Version + +```dockerfile +# In your Dockerfile or at build time +ARG PYTHON_VERSION=3.11 +docker build --build-arg PYTHON_VERSION=3.11 -t myapp:latest . +``` + +### Add System Dependencies + +If your app needs system libraries (e.g., for psycopg2, Pillow): + +```dockerfile +# In the runtime stage, before USER appuser: +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + libpq5 \ + libjpeg62-turbo && \ + rm -rf /var/lib/apt/lists/* +``` + +### Custom Health Check + +```dockerfile +# HTTP endpoint +HEALTHCHECK CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" + +# TCP port check +HEALTHCHECK CMD python -c "import socket; s=socket.socket(); s.connect(('localhost',8000)); s.close()" + +# Custom script +HEALTHCHECK CMD python /app/healthcheck.py +``` + +### Entry Point Options + +```dockerfile +# Gunicorn (production WSGI) +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"] + +# Uvicorn (production ASGI) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + +# FastAPI with auto-reload (development only!) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--reload"] +``` + +## Security Scanning + +```bash +# Scan with Trivy +trivy image myapp:latest + +# Scan with Grype +grype myapp:latest + +# Generate SBOM +syft myapp:latest -o spdx-json > sbom.json +``` + +## Image Size Comparison + +Typical sizes for a FastAPI app with common dependencies: + +| Template | Approximate Size | +|----------|-----------------| +| Dockerfile.uv | ~120MB | +| Dockerfile.pip | ~130MB | +| Dockerfile.distroless | ~90MB | + +## Best Practices + +1. **Pin versions** - Use specific Python and dependency versions +2. **Use .dockerignore** - Exclude `.git`, `__pycache__`, `.venv`, tests +3. **Scan images** - Run Trivy/Grype in CI before deploying +4. **Use secrets properly** - Never bake secrets into images +5. **Multi-arch builds** - Use `docker buildx` for ARM64 support + +## Example .dockerignore + +``` +.git +.gitignore +.dockerignore +Dockerfile* +docker-compose*.yml +*.md +*.pyc +__pycache__ +.pytest_cache +.mypy_cache +.venv +.env +.env.* +tests/ +docs/ +``` + +## License + +MIT