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 barealpineoralpine:latestdrifts. - 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) andCOPY --from=buildthe 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
RUNto reduce layers; clean up within the sameRUNso 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 pkgin oneRUN, thenrm -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. ENTRYPOINTfor the fixed command,CMDfor default args. In entrypoint scripts,exec "$@"so the app becomes PID 1.- Use absolute paths in
WORKDIR; don'tRUN cd … && …. EXPOSEdocuments the listening port; it doesn't publish it.
Security
- Run as non-root. Create a user explicitly and
USERto it if the service doesn't need privileges. Avoidsudo; usegosuif you must drop privileges. - Never bake secrets.
ENV,ARG, andCOPYpersist in image layers and history. UseRUN --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
:latestor 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 updatein a separate layer fromapt-get install. - Don't use shell-form
CMD/ENTRYPOINTfor long-running processes.