Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Authors small, secure, reproducible multi-stage Dockerfiles with build-cache optimization, pinned base images, non-root runtime users, and minimal attack surface. Use this skill when writing, reviewing, hardening, or shrinking a Dockerfile or container image — e.g. "write a Dockerfile", "containerize this app", "my image is too big", "make this container secure / non-root", "optimize Docker build cache", "multi-stage build", "reduce image layers", "fix Docker best practices", or "review my Docke
.claude/skills/jayrha-dockerfile-pro/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 99% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 88% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 67% | 0% |
| case-08 | ✓→✓ | = Same ✓ | 355% | 0% |
Produce production-grade Dockerfiles that are small (minimal final image), secure (non-root, pinned, no secrets, minimal surface), and reproducible (deterministic builds with effective layer caching). Applies to any language runtime (Go, Rust, Node, Python, Java, .NET, etc.) and any registry.
Keywords: Dockerfile, container, multi-stage build, build cache, BuildKit, non-root user, distroless, alpine, slim, image size, layer caching, .dockerignore, HEALTHCHECK, OCI image, supply chain, reproducible build, hadolint, Trivy.
The three goals (small / secure / reproducible) often align, but when they conflict, prioritize security > reproducibility > size.
Follow these steps in order when authoring or reviewing a Dockerfile.
FROM scratch or distroless final stage. Interpreted/runtime (Node/Python/Java/.NET) → use the matching -slim/distroless runtime image. JVM/CLR may need a JRE-only final image.builder stage installs toolchains and compiles/installs dependencies; the final stage copies only the runtime artifacts. Never ship compilers, package caches, or dev headers.image:tag@sha256:...). Pin OS and language package versions. This is what makes builds reproducible.package.json, go.mod, requirements.txt, *.csproj) and install deps before copying application source. Source changes then don't bust the dependency layer.RUN --mount=type=cache,...) for package-manager caches so repeated builds are fast without bloating the image.RUN steps, clean package caches in the same layer, and copy with --chown instead of a separate chown layer.WORKDIR, EXPOSE, ENV, OCI LABELs, HEALTHCHECK, and an exec-form ENTRYPOINT/CMD..dockerignore. Exclude .git, node_modules, build output, secrets, and CI files so the build context stays small and secrets never enter an image.hadolint Dockerfile and a vulnerability scan (e.g. trivy image). See scripts/check_dockerfile.py for a fast static audit you can run with zero dependencies.| App type | Recommended final base | Notes | |---|---|---| | Static Go / Rust binary | gcr.io/distroless/static or scratch | Add CA certs + /etc/passwd if using scratch. | | Dynamically-linked binary | gcr.io/distroless/base or *-slim | Needs libc. | | Node.js | gcr.io/distroless/nodejs22-debian12 or node:22-slim | Distroless has no shell — great for prod, harder to debug. | | Python | python:3.12-slim or gcr.io/distroless/python3 | Prefer slim + venv copy. | | Java | eclipse-temurin:21-jre or gcr.io/distroless/java21 | JRE only, never the JDK, in the final stage. | | .NET | mcr.microsoft.com/dotnet/aspnet:8.0 (or -chiseled) | chiseled images are distroless-style and non-root by default. | | Needs a shell/debug | *-slim or *-alpine | Alpine uses musl — watch for glibc-specific bugs. |
Rule of thumb: distroless or scratch for compiled apps; -slim for interpreted apps. Avoid full :latest / fat base images.
musl libc can break native deps (Python wheels, glibc binaries) and complicate DNS/locale handling.-slim: slightly larger but maximally compatible. Default to -slim unless you have measured a real size win and tested compatibility.FROM node:22.11.0-slim@sha256:<digest>. Tags are mutable; digests are not.AS builder, AS deps, AS runtime.CMD ["node","server.js"] — never the shell form CMD node server.js (it breaks signal handling and PID 1 semantics).USER 10001:10001. A numeric UID lets Kubernetes enforce runAsNonRoot even without /etc/passwd.COPY --chown=10001:10001 to set ownership without an extra layer; never chmod -R 777.apt-get update && apt-get install -y --no-install-recommends X && rm -rf /var/lib/apt/lists/*.--no-install-recommends (apt) / --no-cache (apk) / --frozen-lockfile (npm ci) for determinism.RUN --mount=type=secret, never ARG/ENV (they persist in image history and docker history leaks them).HEALTHCHECK for long-running services so orchestrators can detect liveness.WORKDIR explicitly; never rely on /.tini or --init (or a runtime that reaps zombies) when your process spawns children.references/best-practices.md for the full annotated checklist and rationale.USER.ADD for local files — use COPY. Reserve ADD for remote URLs/tar auto-extraction (and prefer not to).ARG/ENV or copied .env files — they leak via docker history and image layers.apt-get upgrade / unpinned latest — destroys reproducibility.RUN chmod/chown after COPY — doubles the data on disk across layers. Use COPY --chown.COPY . .) before installing deps — busts the cache on every source edit.CMD/ENTRYPOINT — your process won't receive SIGTERM, so graceful shutdown breaks..dockerignore — bloats context, slows builds, risks leaking .git/credentials.references/best-practices.md — exhaustive, annotated best-practice checklist with rationale, BuildKit cache-mount and secret-mount recipes, and a security/supply-chain section.examples/go-multistage.Dockerfile — minimal scratch-based Go image (non-root, pinned, static).examples/node-multistage.Dockerfile — Node.js multi-stage with npm ci, cache mounts, distroless final.examples/python-multistage.Dockerfile — Python slim with venv copy and non-root user.templates/Dockerfile.template — language-agnostic fill-in-the-blanks multi-stage template.templates/dockerignore.template — sensible default .dockerignore.scripts/check_dockerfile.py — zero-dependency static auditor that flags root users, unpinned bases, secrets in ARG, shell-form CMD, missing .dockerignore, and more.Use the examples/templates as the starting skeleton, then apply the workflow and run scripts/check_dockerfile.py plus hadolint before finishing.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 15,104 | 13,627 | -10% | 1 | 1 | 0% | 2,818 | 4,466 | +58% | 0 | 0 | — |
case-02 | fail→fail | 13,589 | 10,054 | -26% | 1 | 1 | 0% | 2,774 | 3,974 | +43% | 0 | 0 | — |
case-03 | fail→pass | 15,016 | 14,845 | -1% | 1 | 1 | 0% | 2,987 | 4,982 | +67% | 0 | 0 | — |
case-04 | fail→fail | 14,877 | 15,127 | +2% | 1 | 1 | 0% | 2,825 | 5,203 | +84% | 0 | 0 | — |
case-05 | pass→pass | 10,351 | 8,637 | -17% | 1 | 1 | 0% | 1,837 | 3,664 | +99% | 0 | 0 | — |
case-06 | pass→pass | 8,522 | 5,297 | -38% | 1 | 1 | 0% | 1,535 | 2,889 | +88% | 0 | 0 | — |
case-07 | pass→pass | 15,170 | 12,412 | -18% | 1 | 1 | 0% | 2,601 | 4,352 | +67% | 0 | 0 | — |
case-08 | pass→pass | 2,772 | 3,981 | +44% | 1 | 1 | 0% | 585 | 2,662 | +355% | 0 | 0 | — |
case-09 | pass→pass | 11,011 | 11,184 | +2% | 1 | 1 | 0% | 2,015 | 3,969 | +97% | 0 | 0 | — |
case-10 | pass→pass | 8,621 | 5,153 | -40% | 1 | 1 | 0% | 1,505 | 2,815 | +87% | 0 | 0 | — |
case-11 | pass→pass | 6,888 | 21,334 | +210% | 1 | 1 | 0% | 1,259 | 2,812 | +123% | 0 | 0 | — |
case-12 | pass→pass | 9,469 | 12,281 | +30% | 1 | 1 | 0% | 1,703 | 3,553 | +109% | 0 | 0 | — |
case-13 | pass→pass | 6,860 | 6,673 | -3% | 1 | 1 | 0% | 1,211 | 3,234 | +167% | 0 | 0 | — |
case-14 | pass→pass | 3,170 | 7,201 | +127% | 1 | 1 | 0% | 562 | 3,466 | +517% | 0 | 0 | — |
case-15 | pass→pass | 14,537 | 6,381 | -56% | 1 | 1 | 0% | 2,325 | 3,012 | +30% | 0 | 0 | — |
case-16 | pass→pass | 8,824 | 8,563 | -3% | 1 | 1 | 0% | 1,698 | 3,637 | +114% | 0 | 0 | — |
case-17 | pass→pass | 5,084 | 6,914 | +36% | 1 | 1 | 0% | 1,054 | 3,155 | +199% | 0 | 0 | — |
case-18 | pass→pass | 4,758 | 5,409 | +14% | 1 | 1 | 0% | 824 | 2,907 | +253% | 0 | 0 | — |
case-19 | pass→pass | 8,598 | 5,127 | -40% | 1 | 1 | 0% | 1,576 | 2,938 | +86% | 0 | 0 | — |
case-20 | pass→pass | 7,176 | 8,940 | +25% | 1 | 1 | 0% | 1,670 | 3,980 | +138% | 0 | 0 | — |
case-21 | pass→pass | 9,203 | 10,442 | +13% | 1 | 1 | 0% | 1,892 | 4,081 | +116% | 0 | 0 | — |
case-22 | pass→pass | 5,498 | 8,423 | +53% | 1 | 1 | 0% | 1,201 | 3,720 | +210% | 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 +5 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.