Yes. The official Node.js Docker images create a user called node with uid 1000, but the container still runs as root until you switch with a USER instruction or the user flag. Add USER node near the end of your Dockerfile, and make sure the app files are readable by that account.
What the image gives you
The maintainers of the Node.js images document the node user as the intended unprivileged account. They also note it is neither a build-time nor a run-time dependency, so you can remove or rename it if your app does not rely on it.
The image does not turn it on for you. A bare FROM node:24-slim followed by CMD ["node", "server.js"] runs your server as root inside the container. Root in a container is not root on the host in the normal case, but it widens what an attacker can do after a code execution bug: write anywhere in the filesystem, bind low ports, and use any capability the runtime grants.
The two ways to switch
In the Dockerfile, set the user after the steps that need root:
FROM node:24-slim
WORKDIR /home/node/app
COPY --chown=node:node package*.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]
At run time you can override instead, with the user flag on docker run, or runAsUser in a Kubernetes security context. Doing it in the image is safer because every consumer gets the same default.
A numeric form such as USER 1000 is worth considering. Tools that check "is this user non-root" can verify a number without resolving a name from the passwd file.
Fixing the "permission denied" that follows
Most breakage after switching comes from file ownership, not from the user itself. Three common causes:
| Symptom | Cause | Fix |
|---|---|---|
| EACCES writing logs or cache | App writes into a root-owned directory | Write to /tmp or a volume owned by node |
| Cannot read copied files | COPY made them root-owned | Use COPY with the chown flag, as above |
| npm global install fails | Global prefix is root-owned | Set NPM_CONFIG_PREFIX to a directory under /home/node |
The Node.js image docs suggest exactly that last fix: point the npm global prefix at a directory in the non-root user's home.
Changing the uid or name
If your platform expects a different uid, the image docs show how to rename or renumber the user on Debian based variants with usermod and groupmod, and on Alpine by deleting the user and recreating it with adduser. Do this in an early layer, before copying files, or ownership will not match.
Try it yourself
Run a quick check on any image you build: start it and run the id command inside it. If the output says uid 0, the switch did not take effect. A layered image where a later FROM resets the user is a common reason.
Where Safeguard fits
A non-root default is a configuration choice, not a vulnerability that a scanner reports. Safeguard's dependency scanning and SBOM generation cover what is inside the image and your lockfile, and policies and gates can fail a pull request on findings you choose. You can try a free scan on a repository.
Quick check
Is the node user created in every Node.js image variant?
The Node.js image documentation describes the node user for its images with uid 1000. Verify on the exact tag you use by running id node inside it, since variants and future releases can change.
Do I still need a read only filesystem if I use USER node?
They solve different problems. A non-root user limits what the process may write and do. A read only root filesystem blocks writes to the image layers entirely. Many teams use both, with a small writable temp directory.
Sources
Last reviewed: 19 October 2026.