Give the container user an explicit numeric UID and GID, switch to it with USER, and set ownership of copied files with COPY and the chown option using the same numbers. Docker warns that user IDs created in an image are not deterministic, so a fixed number keeps permissions predictable.
Why the number matters
Docker's best practices page says users and groups in an image are assigned a non-deterministic UID and GID, and suggests an explicit one if it matters. It matters more often than people think. Volume mounts, read only filesystems and Kubernetes settings that require a non-root numeric user all compare numbers, not names.
RUN groupadd -g 10001 app && useradd -u 10001 -g 10001 -r app
USER 10001:10001
Pick a high number that will not collide with host users. Using the same number across your services makes shared volume permissions simpler.
What COPY does to ownership
The Dockerfile reference says that without the chown option, copied files get UID and GID zero, which means root. A non-root process may then be unable to write where it needs to, or you end up tempted to loosen permissions. Set ownership at copy time instead.
# COPY with the chown option set to 10001:10001
COPY ./app /app
The reference adds two details. Names are resolved through the passwd and group files inside the image, so the build fails if the name is missing there. Numeric IDs skip that lookup, and a numeric UID given without a group also uses the same number as the GID. When you combine copy with the link option, you must use numeric IDs.
What USER covers
USER sets the default user for the rest of the build stage. It applies to RUN instructions and to the runtime ENTRYPOINT and CMD. Two traps from the reference: if you name a group, the user gets only that group, and a user with no primary group runs with the root group. State both values to avoid surprises.
Make the app writable only where needed
Most web services need to write to a temp directory and maybe a cache. Create those paths in the build, give them to the app user, and keep the rest of the tree owned by root and read only for the app. A compromised process then cannot edit its own code.
| Path | Owner | App user can write |
|---|---|---|
| Application code | root | No |
| Temp and cache directories | app user | Yes |
| Mounted data volume | matches UID | Yes, if intended |
Checking your work
Run the image and print the identity with the id command, then try to write to a code directory and confirm it fails. For the wider checklist, see running a container as non-root.
Quick check
Is a username in USER enough?
It works, but the numeric ID is less ambiguous. Some policies check numbers, and a name needs a matching entry in the image's passwd file.
Why are my copied files owned by root?
COPY gives files UID and GID zero unless you use the chown option.
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.