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>
236 lines
5.1 KiB
Markdown
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
|
|
```
|