# Dockerfile best practices — Claude Code rules You are authoring or editing Dockerfiles. Follow Docker's official best practices below. ## Images & base - Start from a current **official** (or Verified Publisher) image, and pick the **minimal** variant that fits — smaller base = faster pulls and smaller attack surface. Alpine is a common small choice (~6 MB). - **Pin the version**, and ideally the digest, for reproducible builds: `FROM alpine:3.21@sha256:a8560b…`. A bare `alpine` or `alpine:latest` drifts. - Don't install packages you don't need. Fewer deps = smaller, faster, safer. - Rebuild regularly with `--pull` (or `--pull --no-cache`) to pick up base-image and dependency security patches — images are immutable snapshots. ## Multi-stage builds - Use multi-stage builds to separate the **build** environment from the **final** runtime image. Copy only the artifacts you need into the last stage. - Name stages (`FROM node:22 AS build`) and `COPY --from=build` the output, so compilers, dev deps, and source never ship in the runtime image. - Factor common setup into reusable stages (DRY) to avoid rebuilding it. ## Caching & layer hygiene - Each instruction is a layer. When a layer changes, **every** layer after it is rebuilt. Order instructions **least-changing → most-changing**. - Install dependencies (which change rarely) *before* copying source (which changes constantly): copy the manifest, install, then copy the rest. - Combine related commands into a single `RUN` to reduce layers; clean up within the same `RUN` so the cleanup actually shrinks the layer. - Sort multi-line args alphanumerically so they're easy to scan and diff. ## RUN / packages - For apt: `RUN apt-get update && apt-get install -y --no-install-recommends pkg` in **one** `RUN`, then `rm -rf /var/lib/apt/lists/*`. Splitting update from install causes stale-cache bugs. - Pin package versions (`pkg=1.3.*`) when you need determinism. - Prepend `set -o pipefail &&` before piped commands so a failure mid-pipe fails the build. ## COPY / ADD - Prefer **COPY** for files from the build context or another stage — it's explicit and predictable. - Reserve **ADD** for what only ADD does: remote URLs with checksum validation, or auto-extracting local tarballs. - Copy specific paths, not the whole context, to keep cache tight. ## Runtime form (CMD / ENTRYPOINT / etc.) - Use **exec form**: `CMD ["node", "server.js"]` / `ENTRYPOINT ["app"]` — exec form receives Unix signals correctly. Shell form does not. - `ENTRYPOINT` for the fixed command, `CMD` for default args. In entrypoint scripts, `exec "$@"` so the app becomes PID 1. - Use absolute paths in `WORKDIR`; don't `RUN cd … && …`. - `EXPOSE` documents the listening port; it doesn't publish it. ## Security - **Run as non-root.** Create a user explicitly and `USER` to it if the service doesn't need privileges. Avoid `sudo`; use `gosu` if you must drop privileges. - **Never bake secrets.** `ENV`, `ARG`, and `COPY` persist in image layers and history. Use `RUN --mount=type=secret,id=…` so the secret exists only during that build step and is never written to a layer. - Add a `.dockerignore` (like `.gitignore`) to keep `.git`, secrets, `node_modules`, and build cruft out of the context. ## Don't - Don't use `:latest` or unpinned bases. - Don't run the app as root by default. - Don't pass secrets via `ARG` / `ENV` / `COPY`. - Don't run multiple concerns in one container — one concern per container. - Don't `apt-get update` in a separate layer from `apt-get install`. - Don't use shell-form `CMD`/`ENTRYPOINT` for long-running processes.