Dockerfile Optimizer
Shrink and harden an existing Dockerfile — multi-stage builds, cache-friendly layer order, a lean pinned base image, a .dockerignore, and a non-root runtime user — without changing what the image runs. Use when an image is too large, builds are slow because the cache never hits, or a scan flags the container running as root.
npx agentscamp add skills/dockerfile-optimizerInstall to ~/.claude/skills/dockerfile-optimizer/SKILL.md
Takes a working Dockerfile and makes it smaller, faster to build, and safer to run: a multi-stage build that keeps compilers out of the final image, layer order that puts dependency manifests before source so the cache hits, a lean pinned base, a .dockerignore, and a non-root USER — behavior-preserving and verified with a real build and an image-size comparison.
Take a Dockerfile that already works and make it smaller, faster to build, and safer to run — without changing what the container actually does. This skill reads the existing Dockerfile and build context, then applies the standard, high-leverage optimizations: multi-stage builds so build tools never ship in the final image, layer ordering that lets the cache hit on unchanged dependencies, a lean and pinned base image, a .dockerignore, and a non-root runtime user.
When to use this skill
- The image is large (hundreds of MB to gigabytes) and you want it smaller.
- Builds are slow because a small source change reinstalls all dependencies (cache never hits).
- A scanner or reviewer flagged the container running as
root, an unpinned:latestbase, or secrets in a layer. - You're preparing an image for production and want it lean and reproducible.
NOTE
This is behavior-preserving. The optimized image must run the same command, expose the same ports, and produce the same result. Don't change the app's runtime behavior, upgrade its language version, or swap frameworks — only how the image is built and packaged.
Instructions
- Read the current state. Read the
Dockerfile, any.dockerignore, and the build context layout. Identify the language/runtime, the build steps, and the finalCMD/ENTRYPOINT. Build once to get a baseline:docker build -t img:before .and record the size withdocker images img:before. - Introduce (or tighten) a multi-stage build. Put compilation, dev dependencies, and toolchains in a
builderstage; copy only the produced artifacts (binary,dist/, wheels,node_modulesfor prod) into a minimal final stage. The final image should contain the runtime and the app — not compilers or package caches. - Choose a lean, pinned base. Prefer a slim or distroless runtime image over a full OS image where the app allows it. Pin to a specific tag (and ideally a digest) — never
:latest. Match the base to the deployment target's architecture. - Order layers for cache hits. Copy dependency manifests first, install dependencies, then copy application source. This keeps the expensive install layer cached across ordinary code changes. Use BuildKit cache mounts (
RUN --mount=type=cache,...) for package-manager caches where supported. - Keep layers clean. Combine related
RUNsteps, and in the same layer remove what you added — clear apt/apk lists (rm -rf /var/lib/apt/lists/*), don't--no-install-recommends-forget, and avoid leaving package caches. NeverCOPYsecrets or.gitinto a layer; use build secrets or runtime env instead. - Add a
.dockerignore. Exclude.git,node_modules(when installed inside the image), build output, test fixtures, local env files, and docs. A smaller build context means faster builds and no accidental secret leakage. - Run as non-root. Create a dedicated user/group and add
USERbefore the finalCMD. Ensure the app's files and any writable dirs are owned appropriately. Add aHEALTHCHECKif the platform uses it. - Verify. Build the optimized image (
docker build -t img:after .), confirm it runs and behaves identically, and compare sizes. Report the before/after image size and build-time change with real numbers. If the project uses a scanner (Docker Scout, Trivy, Grype), run it and note the delta.
Examples
A Node service that copied everything before installing, ran as root, and shipped the full build toolchain:
# syntax=docker/dockerfile:1
# ---- builder ----
FROM node:22-bookworm-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
# ---- runtime ----
FROM node:22-bookworm-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]Report the outcome: image size before → after, whether the build cache now survives a source-only change, the non-root user added, and any scanner findings resolved. Note anything you intentionally left alone (e.g. a base image the app pins for a native dependency).
Frequently asked questions
- Why does reordering COPY lines make the build faster?
- Docker caches each layer and invalidates every layer after the first one whose inputs changed. If you COPY your whole source tree before installing dependencies, any code change busts the dependency-install layer and reinstalls everything. Copy just the dependency manifests (package.json, go.mod, requirements.txt, pom.xml) first, install, then copy the rest of the source — now editing a source file reuses the cached install layer and the build is seconds instead of minutes.
Related
- Dev Container DesignerDesign a reproducible dev environment (Dev Container / Docker) so onboarding is one command and 'works on my machine' dies — by detecting the project's real stack and versions, authoring a devcontainer.json (+ Dockerfile/compose) that pins the runtime to what the repo targets, wires dependent services, caches dependencies, and injects secrets instead of baking them. Use when new contributors struggle to set up the project, when environment drift causes inconsistent behavior, or when standardizing tooling across a team.
- GitHub Actions OptimizerMake a GitHub Actions workflow faster, cheaper, and harder to attack — by profiling where wall-clock and billed minutes actually go, then adding content-keyed caching, matrix/job parallelism, run-cancellation, and path filters, and hardening the supply chain (SHA-pinned actions, least-privilege GITHUB_TOKEN, safe fork-PR handling). Use when CI is slow or queues, when a repo burns Actions minutes, or before trusting a workflow that runs on untrusted pull requests.
- Cold Start OptimizerCut cold-start latency for serverless functions and slow-booting apps by measuring the init breakdown, then attacking the dominant phase — artifact size, eager imports, eager connections, or under-provisioned memory — instead of reflexively buying provisioned concurrency. Use when serverless p99 spikes on the first request, when a function times out during init, or when scale-to-zero is hurting user-facing latency.