A container can still run as root after you add USER because USER only sets the default for later instructions in the current build stage. Typical causes are the line sitting in the wrong stage, a later USER switch back to root, a start script that needs root, or an override when the container starts.
Step 1: prove it
Do not guess. Run the built image and print who it is.
docker run myimage id
If the output shows uid zero, work through the causes below. This test also overrides any default command, so for the real behavior also exec into a running container and run the same command.
Step 2: check which stage has USER
The Dockerfile reference says USER sets the default for the rest of the current build stage. In a multi-stage build, a USER line in the build stage does nothing for the final stage. Put it in the stage that ships.
FROM build AS builder
USER 10001
RUN make app
FROM runtime
COPY app /app
In that sketch the final stage never sets a user, so it runs as root. Move the line after the last FROM.
Step 3: look for a switch back
Docker's best practices page advises against switching USER back and forth often. Search the final stage for a later USER root line, often added to install one more package. Do privileged installs first, then drop privileges as the last step before the start command.
Step 4: check start scripts and capabilities
Some entrypoint scripts change ownership, bind low ports or write to system paths, so authors keep them running as root. Rework them: fix ownership at build time with COPY and the chown option, listen on a high port, and write only to directories the app user owns. The best practices page also says to avoid sudo because of unpredictable terminal and signal behavior.
Step 5: check how it is launched
The image sets a default. Whoever runs it can change that default at start time, and orchestrators can enforce or override users too. Review your compose files and deployment manifests for a user setting, and set a policy that rejects pods or containers that run as root.
Step 6: scan for the pattern
A Dockerfile review can catch a missing USER, but only the built image proves the result. Add a CI step that runs the identity command and fails on uid zero, and use your scanner to flag images whose configuration runs as root.
| Symptom | Likely cause |
|---|---|
| id shows zero | No USER in final stage |
| USER present but root at runtime | Run time override or later USER root |
| App cannot write files | Copied files owned by root |
| Cannot bind port 80 | Use a high port instead |
For the full hardening list, read running a container as non-root.
Try it yourself
Click through it at your own pace, or open it full screen.
Quick check
Does a non-root user make the container safe?
It reduces what a compromised process can do, but it is one layer. Combine it with dropped capabilities, read only filesystems and patched images.
Where should USER go in a multi-stage Dockerfile?
In the final stage, as late as possible, after any steps that need root.
Safeguard
Safeguard does SBOM and dependency scanning, and its policies and gates can run in CI so a bad image does not reach production. You can try a free scan before deciding anything.
Sources
Last reviewed: 19 October 2026.