Files
claude-skills-reference/engineering/docker-development/references/dockerfile-best-practices.md
Leo bf1473b1be feat(skills): add research-summarizer and docker-development agent skills
research-summarizer (product-team/):
- Structured research summarization for papers, articles, reports
- Slash commands: /research:summarize, /research:compare, /research:cite
- Python tools: extract_citations.py (5 citation formats), format_summary.py (6 templates)
- References: summary-templates.md, citation-formats.md

docker-development (engineering/):
- Dockerfile optimization, compose orchestration, container security
- Slash commands: /docker:optimize, /docker:compose, /docker:security
- Python tools: dockerfile_analyzer.py (15 rules), compose_validator.py (best practices)
- References: dockerfile-best-practices.md, compose-patterns.md

Both skills include .claude-plugin/plugin.json and follow POWERFUL tier conventions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 22:47:16 +01:00

236 lines
5.1 KiB
Markdown

# Dockerfile Best Practices Reference
## Layer Optimization
### The Golden Rule
Every `RUN`, `COPY`, and `ADD` instruction creates a new layer. Fewer layers = smaller image.
### Combine Related Commands
```dockerfile
# Bad — 3 layers
RUN apt-get update
RUN apt-get install -y curl git
RUN rm -rf /var/lib/apt/lists/*
# Good — 1 layer
RUN apt-get update && \
apt-get install -y --no-install-recommends curl git && \
rm -rf /var/lib/apt/lists/*
```
### Order Layers by Change Frequency
```dockerfile
# Least-changing layers first
COPY package.json package-lock.json ./ # Changes rarely
RUN npm ci # Changes when deps change
COPY . . # Changes every build
RUN npm run build # Changes every build
```
### Use .dockerignore
```
.git
node_modules
__pycache__
*.pyc
.env
.env.*
dist
build
*.log
.DS_Store
.vscode
.idea
coverage
.pytest_cache
```
---
## Base Image Selection
### Size Comparison (approximate)
| Base | Size | Use Case |
|------|------|----------|
| `scratch` | 0MB | Static binaries (Go, Rust) |
| `distroless/static` | 2MB | Static binaries with CA certs |
| `alpine` | 7MB | Minimal Linux, shell access |
| `distroless/base` | 20MB | Dynamic binaries (C/C++) |
| `debian-slim` | 80MB | When you need glibc + apt |
| `ubuntu` | 78MB | Full Ubuntu ecosystem |
| `python:3.12-slim` | 130MB | Python apps (production) |
| `node:20-alpine` | 130MB | Node.js apps |
| `golang:1.22` | 800MB | Go build stage only |
| `python:3.12` | 900MB | Never use in production |
| `node:20` | 1000MB | Never use in production |
### When to Use Alpine
- Small image size matters
- No dependency on glibc (musl works)
- Willing to handle occasional musl-related issues
- Not running Python with C extensions that need glibc
### When to Use Slim
- Need glibc compatibility
- Python with compiled C extensions (numpy, pandas)
- Fewer musl compatibility issues
- Still much smaller than full images
### When to Use Distroless
- Maximum security (no shell, no package manager)
- Compiled/static binaries
- Don't need debugging access inside container
- Production-only (not development)
---
## Multi-Stage Builds
### Why Multi-Stage
- Build tools and source code stay out of production image
- Final image contains only runtime artifacts
- Dramatically reduces image size and attack surface
### Naming Stages
```dockerfile
FROM golang:1.22 AS builder # Named stage
FROM alpine:3.19 AS runtime # Named stage
COPY --from=builder /app /app # Reference by name
```
### Selective Copy
```dockerfile
# Only copy the built artifact — nothing else
COPY --from=builder /app/server /server
COPY --from=builder /app/config.yaml /config.yaml
# Don't COPY --from=builder /app/ /app/ (copies source code too)
```
---
## Security Hardening
### Run as Non-Root
```dockerfile
# Create user
RUN groupadd -r appgroup && useradd -r -g appgroup -s /sbin/nologin appuser
# Set ownership
COPY --chown=appuser:appgroup . .
# Switch user (after all root-requiring operations)
USER appuser
```
### Secret Management
```dockerfile
# Bad — secret baked into layer
ENV API_KEY=sk-12345
# Good — BuildKit secret mount (never in layer)
RUN --mount=type=secret,id=api_key \
export API_KEY=$(cat /run/secrets/api_key) && \
./configure --api-key=$API_KEY
```
Build with:
```bash
docker build --secret id=api_key,src=./api_key.txt .
```
### Read-Only Filesystem
```yaml
# docker-compose.yml
services:
app:
read_only: true
tmpfs:
- /tmp
- /var/run
```
### Drop Capabilities
```yaml
services:
app:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if binding to ports < 1024
```
---
## Build Performance
### BuildKit Cache Mounts
```dockerfile
# Cache pip downloads across builds
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# Cache apt downloads
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y curl
```
### Parallel Builds
```dockerfile
# These stages build in parallel when using BuildKit
FROM node:20-alpine AS frontend
COPY frontend/ .
RUN npm ci && npm run build
FROM golang:1.22 AS backend
COPY backend/ .
RUN go build -o server
FROM alpine:3.19
COPY --from=frontend /dist /static
COPY --from=backend /server /server
```
### Enable BuildKit
```bash
export DOCKER_BUILDKIT=1
docker build .
# Or in daemon.json
{ "features": { "buildkit": true } }
```
---
## Health Checks
### HTTP Service
```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
```
### Without curl (using wget)
```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8000/health || exit 1
```
### TCP Check
```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD nc -z localhost 8000 || exit 1
```
### PostgreSQL
```dockerfile
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
CMD pg_isready -U postgres || exit 1
```
### Redis
```dockerfile
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD redis-cli ping | grep PONG || exit 1
```