Yes. Build in one stage with a full Node.js image, then copy only the node binary, node_modules and built output into a plain base image. The final image has no npm or Yarn, so there is no package manager for an attacker to run after a compromise. The official Node.js image maintainers document this pattern.
Why remove the package manager
A package manager in a runtime image is a ready made tool for anyone who gets code execution. It can fetch and run more code from a registry, and it has its own dependency tree that a scanner will report on even though your app never uses it at run time.
Removing it also trims scan noise. Findings in npm's bundled packages stop appearing in the production image, because the packages are no longer there. The Node.js docs also say that in images based on Node.js 26 or later, Yarn is already removed from the official images.
The pattern
The Node.js Docker maintainers show a two stage layout. The first stage installs and builds. The second starts from a bare distribution image, adds the few binaries Node needs, and copies in the results.
FROM node:24-trixie-slim AS builder
WORKDIR /build-stage
COPY package*.json ./
RUN npm ci
COPY . ./
RUN npm run build
FROM debian:trixie-slim
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 1000 node \
&& useradd --uid 1000 --gid node --shell /bin/bash --create-home node \
&& chown node:node ./
COPY --from=builder /usr/local/bin/node /usr/local/bin/
USER node
COPY --from=builder /build-stage/node_modules ./node_modules
COPY --from=builder /build-stage/dist ./dist
CMD ["dumb-init", "node", "dist/index.js"]
This follows the maintainers' example in outline. Their version also copies the image's entrypoint script and sets it as ENTRYPOINT, so use their current text as your starting point and adapt it.
What you give up
| Gain | Cost |
|---|---|
| No package manager at run time | You must create the user and install an init yourself |
| Fewer packages to scan | You own updates to the bare base image |
| Smaller image | Debugging is harder with fewer tools |
Because you chose the bare base, you also own its patches. A digest pinned base needs a rebuild process, or it ages quietly.
Another trap: copying all of node_modules from the build stage also copies dev dependencies. Run a second install with dev packages omitted, or prune, before the copy. npm ci can omit dev dependencies, and setting NODE_ENV to production changes its default.
Try it yourself
Open the container image scan demo and compare what a scan reports before and after a base image change. Look for findings that sit in operating system packages rather than in your app.
Click through it at your own pace, or open it full screen.
Where Safeguard fits
Safeguard generates SBOMs and scans dependencies, so you can compare the component list of the builder stage image against the final one. Policies and gates can block a pull request that adds a risky package. A free scan is a quick way to see your current dependency picture.
Quick check
Will my app break without npm in the image?
Only if it calls npm at run time, for example through npm start or a script that shells out to npm. Switch to node as CMD first. Native modules compiled in the builder stage still work if the runtime base has the same libc.
Is this the same as using a distroless image?
It is similar in spirit. A distroless image also removes the shell and package manager, but it is a ready made base. Here you assemble a minimal base yourself and take on its maintenance.
Sources
Last reviewed: 19 October 2026.