Claude
Skills
Sign in
Back

docker-best-practices

Included with Lifetime
$97 forever

Docker containerization patterns for Python/React projects. Use when creating or modifying Dockerfiles, optimizing image size, setting up Docker Compose for local development, or hardening container security. Covers multi-stage builds for Python (python:3.12-slim) and React (node:20-alpine -> nginx:alpine), layer optimization, .dockerignore, non-root user, security scanning with Trivy, Docker Compose for dev (backend + frontend + PostgreSQL + Redis), and image tagging strategy. Does NOT cover deployment orchestration (use deployment-pipeline).

Image & Video

What this skill does


# Docker Best Practices

## When to Use

Activate this skill when:
- Creating a new Dockerfile for a Python backend or React frontend
- Optimizing existing Docker images for smaller size or faster builds
- Setting up Docker Compose for local development
- Configuring multi-stage builds to separate build and runtime dependencies
- Hardening container security (non-root user, minimal base images)
- Running security scans on Docker images with Trivy
- Designing an image tagging strategy for CI/CD pipelines
- Troubleshooting Docker build failures or runtime issues

Do NOT use this skill for:
- Deployment orchestration or CI/CD pipelines (use `deployment-pipeline`)
- Kubernetes configuration or Helm charts
- Cloud infrastructure provisioning (Terraform, CloudFormation)
- Application code patterns (use `python-backend-expert` or `react-frontend-expert`)

## Instructions

### Multi-Stage Build Strategy

Multi-stage builds keep final images small by separating build-time and runtime dependencies.

**Principle:** Build in a full image, run in a minimal image. Only copy what is needed for runtime.

```
┌──────────────────────────────────┐
│       Stage 1: Builder           │
│  Full SDK, build tools, deps     │
│  Compile, install, build         │
├──────────────────────────────────┤
│       Stage 2: Runtime           │
│  Minimal base image              │
│  COPY --from=builder artifacts   │
│  Non-root user, health check     │
└──────────────────────────────────┘
```

### Python Backend Dockerfile

See `references/python-dockerfile-template` for the complete template.

**Key decisions for Python:**

```dockerfile
# Stage 1: Build dependencies
FROM python:3.12-slim AS builder

WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential libpq-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Stage 2: Runtime
FROM python:3.12-slim AS runtime

# Install only runtime system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 curl \
    && rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser

WORKDIR /app
COPY --from=builder /install /usr/local
COPY src/ ./src/
COPY alembic/ ./alembic/
COPY alembic.ini .

RUN chown -R appuser:appuser /app
USER appuser

EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

**Why these choices:**
- `python:3.12-slim` instead of `alpine` -- avoids musl compatibility issues with binary wheels
- `--no-cache-dir` -- prevents pip cache from bloating the image
- `--prefix=/install` -- isolates installed packages for clean COPY
- `libpq5` at runtime, `libpq-dev` only at build -- minimizes runtime dependencies
- `curl` in runtime -- needed for HEALTHCHECK command

### React Frontend Dockerfile

See `references/react-dockerfile-template` for the complete template.

**Key decisions for React:**

```dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build

# Stage 2: Serve with Nginx
FROM nginx:alpine AS runtime

COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

RUN addgroup -g 1001 -S appgroup && \
    adduser -S appuser -u 1001 -G appgroup && \
    chown -R appuser:appgroup /var/cache/nginx /var/log/nginx /etc/nginx/conf.d && \
    touch /var/run/nginx.pid && chown appuser:appgroup /var/run/nginx.pid

USER appuser
EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD wget -q --spider http://localhost:8080/ || exit 1

CMD ["nginx", "-g", "daemon off;"]
```

**Why these choices:**
- `node:20-alpine` for build -- smallest Node image, only needed at build time
- `nginx:alpine` for serving -- ~7MB base, production-grade static file server
- `npm ci` -- deterministic installs from lockfile, faster than `npm install`
- `--ignore-scripts` -- security measure, prevents running arbitrary scripts during install
- Final image has NO Node.js runtime -- only static files + Nginx

### Base Image Selection Guide

| Use Case | Base Image | Size | Notes |
|----------|-----------|------|-------|
| Python backend | `python:3.12-slim` | ~150MB | Best compatibility with binary wheels |
| Python backend (minimal) | `python:3.12-alpine` | ~50MB | May need musl workarounds for some packages |
| React build stage | `node:20-alpine` | ~130MB | Only used during build |
| React runtime | `nginx:alpine` | ~7MB | Production static file serving |
| Utility/scripts | `alpine:3.19` | ~5MB | For helper containers |

**Rules:**
1. Never use `latest` tag -- always pin major.minor version
2. Prefer `-slim` variants for Python (avoids musl issues)
3. Prefer `-alpine` variants for Node.js and Nginx (smaller images)
4. Update base images monthly for security patches

### Layer Optimization

Docker caches layers. Order instructions from least-changing to most-changing.

**Optimal layer order:**
```dockerfile
# 1. Base image (changes rarely)
FROM python:3.12-slim

# 2. System dependencies (changes monthly)
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 curl && rm -rf /var/lib/apt/lists/*

# 3. Create user (changes never)
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 4. Python dependencies (changes weekly)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 5. Application code (changes every commit)
COPY src/ ./src/

# 6. Runtime config (changes rarely)
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

**Common mistakes to avoid:**
```dockerfile
# BAD: Copying everything before installing dependencies (busts cache)
COPY . .
RUN pip install -r requirements.txt

# GOOD: Copy only requirements first, then install, then copy code
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/
```

**Minimize layers:**
```dockerfile
# BAD: Multiple RUN commands create multiple layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# GOOD: Single RUN command, single layer, clean up in same layer
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*
```

### .dockerignore

Always include a `.dockerignore` to prevent unnecessary files from entering the build context.

```dockerignore
# Version control
.git
.gitignore

# Python
__pycache__
*.pyc
*.pyo
.pytest_cache
.mypy_cache
.ruff_cache
*.egg-info
dist/
build/
.venv/
venv/

# Node
node_modules/
npm-debug.log*
.next/
coverage/

# IDE
.vscode/
.idea/
*.swp
*.swo

# Docker
Dockerfile*
docker-compose*
.dockerignore

# Environment files
.env
.env.*
!.env.example

# Documentation
*.md
docs/
LICENSE

# CI/CD
.github/
.gitlab-ci.yml

# OS
.DS_Store
Thumbs.db
```

**Impact of .dockerignore:**
- Without it: Build context may be 500MB+ (node_modules, .git)
- With it: Build context typically 5-20MB
- Faster builds, no risk of leaking secrets from `.env` files

### Security Hardening

#### Non-Root User

Never run containers as root in production.

```dockerfile
# Create a dedicated user with no shell and no home directory
RUN groupadd -r appuser && \
    useradd -r -g appuser -d /app -s /sbin/nologin appuser

# Set ownership of application files
COPY --chown=appuser:appuser src/ ./src/

# Switch to non-root user
USER appuser
```

#### Security Scanning with Trivy

Scan images for vulnerabilities before deployment.

```bash
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

# Scan image for vulnerabilities
trivy image --severity HIGH,CRITICAL app-backend:latest

# Scan and fail if HIGH/CRITICAL vulnerabilitie

Related in Image & Video