dockerfile-smells · git:20260708.c0884a9 · 2026-07-08 · sha256 de07a1f838cf1074
dockerfile-smells git:20260708.c0884a9A
Immutable. This exact content is served forever at /api/v1/blob/de07a1f838cf1074.
--- name: Dockerfile Smells and Hardening description: This skill should be used when the user mentions "Dockerfile", "docker image size", "container security", "multi-stage build", "distroless", "run as root", "docker layer caching", or is reviewing/optimizing a container image. It provides a catalog of Dockerfile anti-patterns and the hardening principles that fix them. --- # Dockerfile Smells and Hardening ## Purpose A consistent set of Dockerfile anti-patterns and their fixes, so container reviews produce smaller, safer, cache-friendly images the same way every time. This is container-build domain knowledge — the specific smells and the reasons they matter — not generic Docker syntax. ## Smell catalog | Smell | Why it hurts | Fix | |-------|--------------|-----| | Runs as `root` (no `USER`) | container escape = host root; violates PSS/most policies | add `USER <uid>` (numeric, non-root) | | `latest` / unpinned base tag | non-reproducible; silent base drift; supply-chain risk | pin `FROM img@sha256:<digest>` | | Single-stage build | ships compilers, headers, build deps in runtime image | multi-stage: build → slim runtime | | `COPY . .` before dep install | any source edit busts the dependency cache layer | copy manifests, install, *then* copy source | | Secrets baked in a layer | recoverable from image history forever | `RUN --mount=type=secret`; never `ENV TOKEN=` | | `apt-get` with no cleanup | apt lists/caches bloat the layer | `--no-install-recommends` + `rm -rf /var/lib/apt/lists/*` in the same `RUN` | | Missing `.dockerignore` | huge context; `.git`/secrets sent to daemon | add `.dockerignore` | | No `HEALTHCHECK` | orchestrator can't detect a wedged process | add `HEALTHCHECK` where the app exposes a check | | `ADD` for local files / remote URLs | silent auto-extract, unverified downloads | use `COPY`; fetch with verified checksums | | Many chained `RUN` layers | extra layers, larger image | combine related commands, order by volatility | ## Hardening principles (apply in order) 1. **Least privilege at runtime** — non-root numeric `USER`, drop capabilities, read-only root filesystem where possible. 2. **Minimal surface** — distroless or `-slim` runtime base; install only what runs in production. 3. **Reproducibility** — pin base image by digest; pin package versions where feasible. 4. **Cache efficiency** — order layers least-volatile → most-volatile (base, then deps, then source). 5. **No secrets in layers** — build secrets via BuildKit mounts; runtime secrets via the orchestrator, never `ENV`/`ARG` bake-in. ## Layer-ordering rule Put the things that rarely change (base image, system packages, dependency manifests + install) **above** the things that change every commit (application source). One dependency layer that survives across builds saves the most CI time. ## Additional Resources ### Reference Files For the full hardening checklist with the security rationale, consult: - **`references/hardening-checklist.md`** — non-root user, minimal/distroless base, digest pinning, no secrets in layers, HEALTHCHECK, dropping setuid/setgid, read-only root filesystem, and least-capability, each with the concrete threat it addresses. For copy-pasteable multi-stage templates, consult: - **`references/multistage-patterns.md`** — build-vs-runtime split templates per ecosystem (Node, Python, Go, Java/JVM, Rust), cache-mount patterns, and exactly what to copy into the final stage.