Search across all documentation pages
USER, build secrets, and .dockerignore.The shortest path is a static lint plus a scan of the built image.
hadolint Dockerfile
docker buildx build -t myapp:$SHA .
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:$SHAhadolint enforces Dockerfile best practices; Trivy catches vulnerable packages in the final image.
The skill wraps these, adds its own criteria, and returns each finding with a rationale and a fix.
Here is a Dockerfile with the defects the skill flags, followed by the corrected version.
Before:
FROM node:22
WORKDIR /app
COPY . .
RUN npm install
ENV API_TOKEN=sk-live-abc123
EXPOSE 3000
CMD ["node", "server.js"]The skill flags five issues: a mutable base tag, copying the full context before installing dependencies, npm install instead of npm ci, a hardcoded secret in an ENV, and no non-root USER.
After:
# Stage 1: install dependencies with a cacheable layer
FROM node:22.11-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Stage 2: minimal runtime image
FROM node:22.11-bookworm-slim
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]The base is pinned to a patch tag and slim variant.
Dependencies install in their own stage from package.json and the lockfile, so a source-only change reuses the cached layer.
The secret is gone; it belongs in a Kubernetes Secret mounted at runtime, not baked into a layer.
The final stage runs as the built-in unprivileged node user.
Docker caches each layer by the instruction and its inputs; a layer rebuilds only if it or an earlier layer changed.
Put the least-frequently-changing steps first - copy the lockfile and install dependencies before copying source that changes on every commit.
Getting this wrong turns a two-second rebuild into a full reinstall on every push.
Build tooling, compilers, and dev dependencies do not belong in the shipped image.
A deps or build stage produces artifacts; the final stage copies only what runtime needs.
Smaller images pull faster and expose fewer packages to CVEs.
A floating tag like node:22 can change under you between builds, breaking reproducibility and slipping in new vulnerabilities.
Pin to a patch tag such as node:22.11-bookworm-slim, or to a digest (node@sha256:...) when you need byte-for-byte reproducibility.
The skill flags any FROM without a specific tag or digest.
An ENV or ARG secret is readable in the image history via docker history.
Use BuildKit's --secret mount, which exposes the value only during a single RUN and never writes it to a layer.
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ciWithout one, COPY . . drags in .git, node_modules, and local .env files - bloating the image and risking secret leakage.
The skill checks that a .dockerignore exists and excludes those paths.
Copying source before installing dependencies is the most common cache mistake and silently wastes CI minutes on every build.
Using npm install instead of npm ci ignores the lockfile and can pull different versions than tested; the skill flags it.
Setting USER before a RUN that needs to write to a root-owned path fails the build; place USER after privileged setup steps.
A pinned base still goes stale - pair the pin with a scheduled Trivy scan so you learn when the pinned version gains a CVE.
EXPOSE documents a port but does not publish it; do not rely on it for network policy.
hadolint alone is a fast, zero-config Dockerfile linter and is the right baseline when you only need static rules.
Buildpacks (Paketo, pack) skip the Dockerfile entirely and generate optimized, non-root images from source, which suits teams that want to avoid maintaining Dockerfiles at all.
docker scout provides image analysis and base-image recommendations integrated with Docker Engine, overlapping with Trivy on CVE detection.
Choose the review skill when you want consistent, explained findings across many teams; choose buildpacks when you want to remove Dockerfile authorship from the picture.
Should the skill block the build or just warn?
Block on hard security failures like leaked secrets or root users; warn on style and cache issues so teams can triage without a broken pipeline.
Does it detect secrets already committed to layers?
Yes for common patterns in ENV/ARG and via image history scanning, but treat any leaked secret as compromised and rotate it regardless.
How does it handle language-specific images?
The checklist is language-agnostic for security and cache order; the reference fixes adapt the install command (npm ci, pip install --no-cache-dir, go build) per ecosystem.
Can it enforce digest pinning?
Yes - it can require FROM to use a digest, though many teams accept patch tags as a pragmatic middle ground.
Does it replace Trivy?
No - it orchestrates Trivy for CVE findings and adds Dockerfile-structure criteria that a scanner does not check.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026