Skill in engineering
dockerfile-best-practices
Write or review a Dockerfile and compose setup for a production service: multi-stage builds that ship only the runtime, pinned base images, layer order for cache hits, non-root user, minimal image size, correct signal handling and health checks, secrets kept out of layers, and language-specific patterns for Node, Python, Go, and Java. Use when containerising a service, when images are large or slow to build, when a container ignores SIGTERM, or when a security scan flags the image.
- Arguments
- (optional) the Dockerfile or the language and framework to containerise
Install
As a plugin
Bundled in the engineering plugin. Add the marketplace once, then install the bundle; it updates with the marketplace.
/plugin marketplace add KhaledSaeed18/dotclaude/plugin install engineering@dotclaudeAs a single item
Run from the project root; the skill lands under .claude/. Copy it to ~/.claude/ to make it global.
npx shadcn@latest add KhaledSaeed18/dotclaude/dockerfile-best-practicesFiles installed
.claude/skills/dockerfile-best-practices/SKILL.md
What it does
A production image contains exactly what the process needs to run, built reproducibly, running as a non-root user, and stopping cleanly when asked. Everything below follows from those four properties.
Structure: multi-stage
# syntax=docker/dockerfile:1.7
FROM node:22.11-bookworm-slim AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile --prod=false
FROM deps AS build
COPY . .
RUN pnpm build && pnpm prune --prod
FROM node:22.11-bookworm-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
RUN groupadd -r app && useradd -r -g app -d /app app
COPY --from=build --chown=app:app /app/node_modules ./node_modules
COPY --from=build --chown=app:app /app/dist ./dist
COPY --chown=app:app package.json ./
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s CMD node dist/healthcheck.js
ENTRYPOINT ["node", "dist/server.js"]
Build stages hold compilers and dev dependencies; the runtime stage copies artifacts only.
Rules
Base images
- Pin to a specific version tag (
22.11-bookworm-slim), and for reproducible builds pin the digest (@sha256:...) with a tool (Renovate, Dependabot) to bump it. - Prefer
-slimor distroless overalpinefor glibc-dependent runtimes (Node native modules, Python wheels); alpine for Go static binaries is fine. - One process per container; no supervisord.
Layers and cache
- Order from least to most frequently changed: base, system packages, dependency manifests, dependency install, source, build. Copy lockfiles before source so dependency layers cache.
RUNsteps combined with&&and cleanup in the same step (apt-get install ... && rm -rf /var/lib/apt/lists/*); a laterrmdoes not shrink an earlier layer..dockerignorewith.git,node_modules, build output,.env*, tests, docs; without it the build context and the cache invalidation are both wrong.- BuildKit cache mounts for package managers:
RUN --mount=type=cache,target=/root/.npm npm ci.
Security
USERnon-root beforeENTRYPOINT; files copied with--chown. Bind to a port above 1024.- No secrets in
ENV,ARG, or copied files; they persist in layers and history. UseRUN --mount=type=secret,id=npmrcfor build-time secrets and runtime injection (env at run, mounted files, a secrets manager) for the rest. - Read-only root filesystem where possible (
--read-onlywithtmpfsfor/tmp); drop capabilities; no--privileged. - Scan (
docker scout,trivy image) in CI; fail on critical CVEs with a fix available.
Runtime behaviour
ENTRYPOINTin exec form (JSON array) so the process is PID 1 and receives signals;CMDfor default arguments only. Shell form wraps insh -cand swallows SIGTERM.- The application handles SIGTERM: stop accepting, drain in-flight requests, close connections, exit within the orchestrator's grace period (default 30 s). If the runtime cannot (some shell scripts), use
tinias the init (ENTRYPOINT ["tini", "--", ...]). HEALTHCHECK(or the orchestrator's probe) hits a cheap endpoint that checks dependencies the process needs; readiness separate from liveness in Kubernetes.- Logs to stdout/stderr, unbuffered (
PYTHONUNBUFFERED=1); no log files in the container. EXPOSEdocuments the port; it does not publish it.
Size
- Check with
docker image lsanddive. Typical targets: Go static under 20 MB (distroless/static), Node under 200 MB, Python under 250 MB. - Do not install dev dependencies, docs, or test files in the runtime stage.
Language notes
- Node:
npm ci/pnpm install --frozen-lockfile;NODE_ENV=production;nodedirectly, notnpm start(npm does not forward signals); set--max-old-space-sizefrom the container limit or use a version that respects cgroups. - Python:
python:3.12-slim;pip install --no-cache-dir -r requirements.txt(oruv sync --frozen) in a build stage into a venv copied to runtime;PYTHONDONTWRITEBYTECODE=1; run withgunicorn/uvicorndirectly;--workersfrom CPU limit. - Go: build stage
golang:1.23,CGO_ENABLED=0 go build -ldflags="-s -w", runtimegcr.io/distroless/static-debian12:nonroot, copy the binary and CA certs. - Java: build with the JDK, run on a JRE (
eclipse-temurin:21-jre), layered jar (spring-boot:build-imageorjarmode=layertools),-XX:MaxRAMPercentage=75.
Compose for local development
compose.yaml with the service built from the Dockerfile's build stage (target: build), source bind-mounted for hot reload, dependencies (db, cache) with named volumes and health checks, depends_on with condition: service_healthy, env from .env (git-ignored) with an .env.example committed.
Review checklist
Pinned base; multi-stage; lockfile copied before source; .dockerignore; non-root; exec-form ENTRYPOINT; signals handled; health check; no secrets in layers (docker history shows none); scan clean; size within target; builds reproducibly twice with the same digest.
This is the skill as installed, rendered from its source on GitHub. Read it as Markdown.
More in engineering
- adversarial-reviewerReview code through three hostile personas - the Saboteur, the New Hire, and the Security Auditor - each required to find at least one issue. Use when a...
- api-design-reviewReview an API contract (REST or GraphQL) before or while it is implemented, checking resource naming, HTTP semantics, status codes, error shape...
- code-review-responseProcess code-review feedback with technical rigour. Understand each point, check it against the actual codebase, and respond with reasoning or...
- create-agentAuthor a new subagent for this repository end to end by scaffolding it with pnpm new, curating its tool allowlist, setting model, color, and memory in...
- create-commandAuthor a new slash command for this repository end to end by scaffolding it with pnpm new, writing the frontmatter and argument handling, drafting the...
- create-hookAuthor a new Claude Code hook for this repository end to end by scaffolding it with pnpm new, writing the hook script and its settings.json wiring...