How to Fix npm Vulnerabilities in Docker with npm audit fix
Published August 4, 2026 · 9 min read
A Node image scan usually splits findings into two buckets: OS packages from the base image, and npm packages from your dependency tree. This guide focuses on the second bucket — fixing npm vulnerabilities in Docker builds with npm audit fix and a disciplined rebuild, without breaking reproducible installs or accidentally shipping devDependencies.

Why npm findings need a different fix path than OS findings
OS package findings get fixed by rebuilding against a patched base image. npm findings live in your lockfile, which only your team controls — there is no upstream maintainer who will silently patch it for you. Fixing them means updating package-lock.json deliberately and rebuilding the image against the updated file.
Step 1: Audit the lockfile outside the container first
Run the audit locally or in CI against the lockfile directly, before touching the Dockerfile at all:
npm audit --omit=dev # JSON output for scripting or CI gating npm audit --omit=dev --json > audit.json jq '.metadata.vulnerabilities' audit.json
--omit=dev matters here: findings in devDependencies like test runners and bundlers will not ship in a properly built image, so auditing production dependencies only keeps the report focused on what actually matters for the container.
Step 2: Apply non-breaking fixes with npm audit fix
Update the lockfile locally and commit the result — do not run this inside the Dockerfile, where the change would never leave the container:
npm audit fix git diff package-lock.json # Run tests against the updated lockfile before committing npm test git add package-lock.json git commit -m "chore: apply npm audit fixes"
Step 3: Handle findings that need a major version bump
npm audit fix skips fixes that require a breaking semver change. Review those individually rather than forcing them blind:
# See what --force would change before running it npm audit fix --dry-run --force # Or bump one package deliberately and test npm install <package>@latest npm test
Treat --force as a last resort applied to one package at a time with a full test run after, not a blanket command to run whenever the audit looks stubborn.
Step 4: Build with npm ci so the container matches the lockfile exactly
npm install can silently drift from the lockfile inside a build; npm ci installs exactly what is pinned and fails loudly if the lockfile is out of sync:
FROM node:22-slim WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . CMD ["node", "server.js"]
Step 5: Exclude devDependencies from the shipped image
Test frameworks, linters, and bundlers do not need to exist in the running container. Use a multi-stage build so the final image installs production dependencies only:
FROM node:22-slim AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build FROM node:22-slim AS runtime WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --omit=dev && npm cache clean --force COPY --from=build /app/dist ./dist USER node CMD ["node", "dist/index.js"]
See our broader guide to reducing CVEs in Docker images for how this pattern combines with base image choice.
Step 6: Handle vulnerable transitive dependencies
Sometimes the vulnerable package is not one you installed directly but a dependency of a dependency that has not released a compatible update. Use overrides to force a specific version:
{
"name": "myapp",
"overrides": {
"vulnerable-transitive-package": "^2.1.4"
}
}Run npm install once to regenerate the lockfile with the override applied, then run your test suite — an override skips normal dependency resolution, so verify the forced version is actually compatible with the packages that depend on it.
Verifying the fixes landed in the image
An updated lockfile does not guarantee the built image reflects it — confirm with a scan of the actual image, not just the audit output:
docker build --no-cache -t myapp:patched . docker save myapp:patched -o myapp.tar curl -fsSL https://scanrook.io/install.sh | sh scanrook scan --file myapp.tar --format json --out report.json jq -r '.findings[] | select(.package.ecosystem=="npm") | "\(.severity)\t\(.package.name)\t\(.package.version)"' report.json
If a finding still appears after these steps, confirm it is not coming from devDependencies leaking into the image via a missed COPY node_modules in an earlier build stage — that is the most common reason a fixed lockfile does not translate into a clean scan.
Where ScanRook fits
ScanRook reads the packages genuinely installed in the built image — not just what the manifest declares — so it catches cases where devDependencies or stale node_modules slipped past a lockfile fix. Combine it with installed-state scanning and see the docs for CI setup.
Frequently asked questions
How do I fix npm vulnerabilities in a Docker image?
Run npm audit fix against the lockfile, rebuild with npm ci, and exclude devDependencies from the final image.
Should npm audit run inside the Dockerfile?
No — run it in CI against the lockfile and commit the fix, so the update is a reviewable diff instead of a silent in-container change.
Does npm ci fix vulnerabilities?
No, it installs exactly what the lockfile specifies. You still need npm audit fix or a manual bump to update the lockfile first.
Do devDependencies show up in an image scan?
Only if they are actually installed in the scanned image — a multi-stage build with --omit=dev excludes them entirely.
Confirm your npm fixes with ScanRook
Scan the built image, not just the lockfile, to catch devDependencies or stale node_modules that a manifest-only check would miss.