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 :latest base, 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 final CMD/ENTRYPOINT. Build once to get a baseline: docker build -t img:before . and record the size with docker images img:before.
- Introduce (or tighten) a multi-stage build. Put compilation, dev dependencies, and toolchains in a
builder stage; copy only the produced artifacts (binary, dist/, wheels, node_modules for 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
RUN steps, 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. Never COPY secrets or .git into 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
USER before the final CMD. Ensure the app's files and any writable dirs are owned appropriately. Add a HEALTHCHECK if 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).
1---2name: dockerfile-optimizer3description: 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.4---56Take 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.78## When to use this skill910- The image is large (hundreds of MB to gigabytes) and you want it smaller.11- Builds are slow because a small source change reinstalls all dependencies (cache never hits).12- A scanner or reviewer flagged the container running as `root`, an unpinned `:latest` base, or secrets in a layer.13- You're preparing an image for production and want it lean and reproducible.1415> [!NOTE]16> 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.1718## Instructions19201. **Read the current state.** Read the `Dockerfile`, any `.dockerignore`, and the build context layout. Identify the language/runtime, the build steps, and the final `CMD`/`ENTRYPOINT`. Build once to get a baseline: `docker build -t img:before .` and record the size with `docker images img:before`.212. **Introduce (or tighten) a multi-stage build.** Put compilation, dev dependencies, and toolchains in a `builder` stage; copy only the produced artifacts (binary, `dist/`, wheels, `node_modules` for prod) into a minimal final stage. The final image should contain the runtime and the app — not compilers or package caches.223. **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.234. **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.245. **Keep layers clean.** Combine related `RUN` steps, 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. Never `COPY` secrets or `.git` into a layer; use build secrets or runtime env instead.256. **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.267. **Run as non-root.** Create a dedicated user/group and add `USER` before the final `CMD`. Ensure the app's files and any writable dirs are owned appropriately. Add a `HEALTHCHECK` if the platform uses it.278. **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.2829## Examples3031A Node service that copied everything before installing, ran as root, and shipped the full build toolchain:3233```dockerfile34# syntax=docker/dockerfile:135# ---- builder ----36FROM node:22-bookworm-slim AS builder37WORKDIR /app38COPY package.json package-lock.json ./39RUN --mount=type=cache,target=/root/.npm npm ci40COPY . .41RUN npm run build && npm prune --omit=dev4243# ---- runtime ----44FROM node:22-bookworm-slim45WORKDIR /app46ENV NODE_ENV=production47COPY --from=builder /app/node_modules ./node_modules48COPY --from=builder /app/dist ./dist49USER node50EXPOSE 300051CMD ["node", "dist/server.js"]52```5354Report 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).