Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Writing optimized, secure, multi-stage Dockerfiles with language-specific patterns (Python, Node.js, Go, Rust), BuildKit features, and distroless images. Use when containerizing applications, optimizing existing Dockerfiles, or reducing image sizes.
.claude/skills/ancoleman-writing-dockerfiles/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 53% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 108% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 139% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 194% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 146% | 0% |
Create production-grade Dockerfiles with multi-stage builds, security hardening, and language-specific optimizations.
Invoke when:
Ask three questions to determine the approach:
1. What language?
references/python-dockerfiles.mdreferences/nodejs-dockerfiles.mdreferences/go-dockerfiles.mdreferences/rust-dockerfiles.mdreferences/java-dockerfiles.md2. Is security critical?
references/security-hardening.md)3. Is image size critical?
Separate build environment from runtime environment to minimize final image size.
Pattern:
dockerfile# Stage 1: Build FROM build-image AS builder RUN compile application # Stage 2: Runtime FROM minimal-runtime-image COPY --from=builder /app/binary /app/ CMD ["/app/binary"]
Benefits:
Decision matrix:
| Language | Build Stage | Runtime Stage | Final Size | |----------|-------------|---------------|------------| | Go (static) | golang:1.22-alpine | gcr.io/distroless/static-debian12 | 10-30MB | | Rust (static) | rust:1.75-alpine | scratch | 5-15MB | | Python | python:3.12-slim | python:3.12-slim | 200-400MB | | Node.js | node:20-alpine | node:20-alpine | 150-300MB | | Java | maven:3.9-eclipse-temurin-21 | eclipse-temurin:21-jre-alpine | 200-350MB |
Distroless images (Google-maintained):
gcr.io/distroless/static-debian12 → Static binaries (2MB)gcr.io/distroless/base-debian12 → Dynamic binaries with libc (20MB)gcr.io/distroless/python3-debian12 → Python runtime (60MB)gcr.io/distroless/nodejs20-debian12 → Node.js runtime (150MB)See references/base-image-selection.md for complete comparison.
Enable BuildKit for advanced caching and security:
bashexport DOCKER_BUILDKIT=1 docker build . # OR docker buildx build .
Key features:
--mount=type=cache → Persistent package manager caches--mount=type=secret → Inject secrets without storing in layers--mount=type=ssh → SSH agent forwarding for private reposSee references/buildkit-features.md for detailed patterns.
Order Dockerfile instructions from least to most frequently changing:
dockerfile# 1. Base image (rarely changes) FROM python:3.12-slim # 2. System packages (rarely changes) RUN apt-get update && apt-get install -y build-essential # 3. Dependencies manifest (changes occasionally) COPY requirements.txt . RUN pip install -r requirements.txt # 4. Application code (changes frequently) COPY . . # 5. Runtime configuration (rarely changes) CMD ["python", "app.py"]
BuildKit cache mounts:
dockerfileRUN --mount=type=cache,target=/root/.cache/pip \ pip install -r requirements.txt
Cache persists across builds, eliminating redundant downloads.
Essential security practices:
1. Non-root users
dockerfile# Debian/Ubuntu RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app USER appuser # Alpine RUN adduser -D -u 1000 appuser && chown -R appuser:appuser /app USER appuser # Distroless (built-in) USER nonroot:nonroot
2. Secret management
dockerfile# ❌ NEVER: Secret in layer history RUN git clone https://${GITHUB_TOKEN}@github.com/private/repo.git # ✅ ALWAYS: BuildKit secret mount RUN --mount=type=secret,id=github_token \ TOKEN=$(cat /run/secrets/github_token) && \ git clone https://${TOKEN}@github.com/private/repo.git
Build with:
bashdocker buildx build --secret id=github_token,src=./token.txt .
3. Vulnerability scanning
bash# Trivy (recommended) trivy image myimage:latest # Docker Scout docker scout cves myimage:latest
4. Health checks
dockerfileHEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
See references/security-hardening.md for comprehensive hardening patterns.
Create .dockerignore to exclude unnecessary files:
# Version control
.git
.gitignore
# CI/CD
.github
.gitlab-ci.yml
# IDE
.vscode
.idea
# Testing
tests/
coverage/
**/*_test.go
**/*.test.js
# Build artifacts
node_modules/
dist/
build/
target/
__pycache__/
# Environment
.env
.env.local
*.logReduces build context size and prevents leaking secrets.
Three approaches:
Example: Poetry multi-stage
dockerfileFROM python:3.12-slim AS builder RUN --mount=type=cache,target=/root/.cache/pip \ pip install poetry==1.7.1 COPY pyproject.toml poetry.lock ./ RUN poetry export -f requirements.txt --output requirements.txt RUN --mount=type=cache,target=/root/.cache/pip \ python -m venv /opt/venv && \ /opt/venv/bin/pip install -r requirements.txt FROM python:3.12-slim COPY --from=builder /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" USER 1000:1000 CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0"]
See references/python-dockerfiles.md for complete patterns and examples/python-fastapi.Dockerfile.
Key patterns:
npm ci (not npm install) for reproducible buildsnode user (UID 1000)Example: Express multi-stage
dockerfileFROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN --mount=type=cache,target=/root/.npm \ npm ci COPY . . RUN npm run build RUN npm prune --omit=dev FROM node:20-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist USER node CMD ["node", "dist/index.js"]
See references/nodejs-dockerfiles.md for npm/pnpm/yarn patterns and examples/nodejs-express.Dockerfile.
Smallest possible images:
-ldflags="-s -w"/go/pkg/mod and build cacheExample: Distroless static
dockerfileFROM golang:1.22-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download COPY . . RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o main . FROM gcr.io/distroless/static-debian12 COPY --from=builder /app/main /app/main USER nonroot:nonroot ENTRYPOINT ["/app/main"]
See references/go-dockerfiles.md and examples/go-microservice.Dockerfile.
Ultra-small static binaries:
Example: Scratch base
dockerfileFROM rust:1.75-alpine AS builder RUN apk add --no-cache musl-dev WORKDIR /app # Cache dependencies COPY Cargo.toml Cargo.lock ./ RUN --mount=type=cache,target=/usr/local/cargo/registry \ mkdir src && echo "fn main() {}" > src/main.rs && \ cargo build --release --target x86_64-unknown-linux-musl && \ rm -rf src # Build application COPY src ./src RUN --mount=type=cache,target=/usr/local/cargo/registry \ cargo build --release --target x86_64-unknown-linux-musl FROM scratch COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/app /app USER 1000:1000 ENTRYPOINT ["/app"]
See references/rust-dockerfiles.md and examples/rust-actix.Dockerfile.
BuildKit cache mount locations:
| Language | Package Manager | Cache Mount Target | |----------|----------------|-------------------| | Python | pip | --mount=type=cache,target=/root/.cache/pip | | Python | poetry | --mount=type=cache,target=/root/.cache/pypoetry | | Python | uv | --mount=type=cache,target=/root/.cache/uv | | Node.js | npm | --mount=type=cache,target=/root/.npm | | Node.js | pnpm | --mount=type=cache,target=/root/.local/share/pnpm/store | | Go | go mod | --mount=type=cache,target=/go/pkg/mod | | Rust | cargo | --mount=type=cache,target=/usr/local/cargo/registry |
Persistent caches eliminate redundant package downloads across builds.
Validate Dockerfile quality:
bash# Lint Dockerfile python scripts/validate_dockerfile.py Dockerfile # Scan for vulnerabilities trivy image myimage:latest # Analyze image size docker images myimage:latest docker history myimage:latest
Compare optimization results:
bash# Before optimization docker build -t myapp:before . # After optimization docker build -t myapp:after . # Compare bash scripts/analyze_image_size.sh myapp:before myapp:after
See scripts/validate_dockerfile.py for automated Dockerfile linting.
Upstream (provide input):
testing-strategies → Test application before containerizingsecurity-hardening → Application-level security before Docker layerDownstream (consume Dockerfiles):
building-ci-pipelines → Build and push Docker images in CIkubernetes-operations → Deploy containers to K8s clustersinfrastructure-as-code → Deploy containers with Terraform/PulumiParallel (related context):
secret-management → Inject runtime secrets (K8s secrets, vaults)observability → Container logging and metrics collection1. Static binary (Go/Rust) → Smallest image
gcr.io/distroless/static-debian12 or scratch2. Interpreted language (Python/Node.js) → Production-optimized
3. JVM (Java) → Optimized runtime
4. Security-critical → Maximum hardening
5. Development → Fast iteration
❌ Never:
latest tags (unpredictable builds)✅ Always:
python:3.12.1-slim, not python:3)Base image registries:
gcr.io/distroless/*python:*, node:*, golang:*registry.access.redhat.com/ubi9/*Vulnerability scanners:
trivy image myimage:latestdocker scout cves myimage:latestgrype myimage:latestReference documentation:
references/base-image-selection.md → Complete base image comparisonreferences/buildkit-features.md → Advanced BuildKit patternsreferences/security-hardening.md → Comprehensive security guidereferences/ directoryexamples/ directory| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | pass→pass | 10,758 | 19,755 | +84% | 1 | 1 | 0% | 1,578 | 5,335 | +238% | 0 | 0 | — |
case-17 | pass→pass | 12,122 | 10,391 | -14% | 1 | 1 | 0% | 2,171 | 5,571 | +157% | 0 | 0 | — |
case-01 | fail→pass | 19,405 | 9,300 | -52% | 1 | 1 | 0% | 3,489 | 5,326 | +53% | 0 | 0 | — |
case-02 | fail→pass | 16,044 | 30,432 | +90% | 1 | 1 | 0% | 3,183 | 6,631 | +108% | 0 | 0 | — |
case-03 | fail→pass | 14,053 | 12,900 | -8% | 1 | 1 | 0% | 2,592 | 6,207 | +139% | 0 | 0 | — |
case-04 | pass→pass | 14,259 | 8,910 | -38% | 1 | 1 | 0% | 2,558 | 5,622 | +120% | 0 | 0 | — |
case-05 | fail→pass | 9,828 | 9,206 | -6% | 1 | 1 | 0% | 1,860 | 5,470 | +194% | 0 | 0 | — |
case-06 | pass→pass | 20,072 | 13,363 | -33% | 1 | 1 | 0% | 2,275 | 6,196 | +172% | 0 | 0 | — |
case-08 | pass→pass | 13,143 | 9,285 | -29% | 1 | 1 | 0% | 2,254 | 5,398 | +139% | 0 | 0 | — |
case-09 | pass→pass | 5,363 | 4,074 | -24% | 1 | 1 | 0% | 1,043 | 4,638 | +345% | 0 | 0 | — |
case-10 | pass→pass | 12,088 | 6,263 | -48% | 1 | 1 | 0% | 2,111 | 4,771 | +126% | 0 | 0 | — |
case-11 | pass→pass | 11,118 | 14,250 | +28% | 1 | 1 | 0% | 1,994 | 6,318 | +217% | 0 | 0 | — |
case-12 | fail→pass | 12,920 | 9,641 | -25% | 1 | 1 | 0% | 2,001 | 4,915 | +146% | 0 | 0 | — |
case-13 | pass→pass | 6,696 | 4,165 | -38% | 1 | 1 | 0% | 1,230 | 4,478 | +264% | 0 | 0 | — |
case-14 | fail→fail | 6,526 | 4,920 | -25% | 1 | 1 | 0% | 1,154 | 4,606 | +299% | 0 | 0 | — |
case-15 | fail→pass | 5,802 | 4,032 | -31% | 1 | 1 | 0% | 995 | 4,434 | +346% | 0 | 0 | — |
case-16 | pass→pass | 4,457 | 3,694 | -17% | 1 | 1 | 0% | 654 | 4,314 | +560% | 0 | 0 | — |
case-18 | pass→pass | 15,441 | 11,414 | -26% | 1 | 1 | 0% | 2,557 | 5,680 | +122% | 0 | 0 | — |
case-19 | pass→pass | 7,736 | 4,109 | -47% | 1 | 1 | 0% | 1,194 | 4,459 | +273% | 0 | 0 | — |
case-20 | pass→pass | 11,728 | 12,467 | +6% | 1 | 1 | 0% | 2,272 | 6,249 | +175% | 0 | 0 | — |
case-21 | pass→pass | 6,606 | 8,737 | +32% | 1 | 1 | 0% | 1,348 | 5,472 | +306% | 0 | 0 | — |
case-22 | pass→pass | 6,566 | 7,070 | +8% | 1 | 1 | 0% | 1,300 | 5,367 | +313% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.