USER reference
Last reviewed on 2026-05-02
How USER changes the identity of subsequent build steps and the runtime process, why it matters for security, and how to avoid the file-ownership traps.
The USER instruction sets the user (and optionally the group) for all subsequent RUN, CMD, and ENTRYPOINT instructions in the Dockerfile, as well as the default user inside the container when it starts. Switching away from root before the container runs is the single most effective hardening step you can apply to a Docker image.
Syntax
USER <user>[:<group>]
USER <UID>[:<GID>]
You can refer to the user by name or by numeric ID. Numeric IDs are preferred in production: an orchestrator can verify them without resolving /etc/passwd inside the image, and Kubernetes' runAsNonRoot: true security context can only check numeric UIDs.
Why it matters
By default, processes inside a container run as root (UID 0). Container isolation is reasonably strong, but a process running as root inside a container can still:
- Write anywhere in the writable filesystem layers.
- Bind privileged ports below 1024.
- Exploit kernel vulnerabilities with a wider blast radius if a sandbox escape exists.
- Trip security policies (PodSecurityAdmission "restricted", OpenShift defaults, EKS hardening profiles) that refuse to schedule root containers.
Switching to a non-root user removes most of those concerns at near-zero cost.
Worked example: create and use a dedicated user
FROM debian:12-slim
# Install the application as root
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Create a system user with a fixed UID/GID
RUN groupadd --system --gid 10001 app \
&& useradd --system --uid 10001 --gid app --create-home --home-dir /app app
WORKDIR /app
COPY --chown=app:app ./bin/server /app/server
# Drop privileges before running
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["/app/server"]
Several details matter here. The user is created with --system, which assigns it a UID outside the normal interactive range. The UID and GID are explicit (10001) so an external policy can pin to them. COPY --chown hands files over to the new user during the copy step rather than running a separate chown command, which would create an extra writable layer. The final USER uses the numeric form so Kubernetes runAsNonRoot can verify it without consulting /etc/passwd.
Distroless and scratch images
Distroless images do not have a shell or useradd, so you cannot create a user inside them. The standard pattern is to create the user in a builder stage, copy /etc/passwd and /etc/group entries (or the relevant lines) into the final stage, and then USER by numeric ID:
FROM debian:12-slim AS builder
RUN groupadd --system --gid 10001 app \
&& useradd --system --uid 10001 --gid app app
# ... build the binary ...
FROM gcr.io/distroless/static-debian12
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder --chown=10001:10001 /out/server /server
USER 10001:10001
ENTRYPOINT ["/server"]
Many official distroless images already ship a nonroot user (UID 65532); using the :nonroot tag of those images lets you skip the user-creation dance entirely.
Creating the user on Alpine and BusyBox images
Alpine ships BusyBox's adduser and addgroup, not the shadow-utils useradd used above. The flags differ, and a Dockerfile copied from a Debian example will fail on Alpine with useradd: not found:
FROM alpine:3.20
RUN addgroup --system --gid 10001 app \
&& adduser --system --uid 10001 --ingroup app --home /app --disabled-password app
WORKDIR /app
COPY --chown=10001:10001 ./bin/server /app/server
USER 10001:10001
ENTRYPOINT ["/app/server"]
The key differences: --ingroup replaces --gid on adduser, --disabled-password replaces --no-create-home-style flags, and there is no --create-home — --home creates the directory on its own. If you need one Dockerfile to work on both families, create the user in a builder stage and copy the /etc/passwd and /etc/group lines forward, as in the distroless example above.
Choosing a UID
Pick a fixed, high, numeric UID and use it consistently. 10001 is a common convention; distroless uses 65532. Three rules keep it out of trouble:
- Numeric, not a name. Kubernetes'
runAsNonRootcheck and many admission policies read the image config directly and cannot resolve a username against/etc/passwd. Hadolint raisesDL3066for exactly this: non-numeric user-id may not be resolvable by host system. - High enough to avoid collisions. UIDs below 1000 are reserved for system accounts inside the image, and low UIDs are more likely to collide with a real account on the host when a volume is mounted.
- Stable across rebuilds. If the UID changes between image versions, every persistent volume written by the old version becomes unreadable to the new one.
Running a container as a different user
The USER in the Dockerfile is a default. Both the runtime and an interactive session can override it, which is how you connect to a container as a user other than root — or as root when the image defaults to something else:
# Start a container as a specific UID:GID, ignoring the image's USER
docker run --rm --user 10001:10001 myapp
# Match the host user, so files written to a bind mount stay yours
docker run --rm -v "$PWD:/work" --user "$(id -u):$(id -g)" myapp
# Open a shell in a running container as a non-root user
docker exec -it --user 10001 my-container sh
# Open a shell as root for debugging, even though the image drops privileges
docker exec -it --user 0 my-container sh
The --user "$(id -u):$(id -g)" form is the standard fix for the "files created in my bind mount are owned by root" problem on Linux. Note that the UID does not need to exist in the container's /etc/passwd; the process simply runs with an unmapped numeric ID, which is harmless for most workloads but will make whoami fail and leave $HOME unset.
Scope and ordering
USERapplies to every instruction after it. To run something as root and then drop, declare your build steps first and placeUSERjust before the runtime stage's exposed surface.- It can be set multiple times. The most recent
USERwins for the layer that follows. - A
USERset in a builder stage does not carry to a later stage. Each stage starts fresh. - The runtime
USERcan be overridden withdocker run --useror with a KubernetessecurityContext. Treat the Dockerfile setting as a sensible default, not a security boundary on its own.
File ownership pitfalls
The most frequent failure mode is files written as root in earlier RUN instructions that the non-root user cannot read or write. Two reliable patterns avoid this:
- Use
COPY --chownfor application files. A singleCOPY --chown=app:app ./build /apphands ownership over at copy time without an extra layer. - Set
WORKDIRafterUSERif the directory may not exist yet. AWORKDIRcreates missing directories owned by the current user, so when it runs afterUSERit produces a writable home.
If the application needs to write to a specific path at runtime (a cache, an upload directory), declare it with VOLUME or document it for the orchestrator and ensure it is owned by the non-root UID.
Common mistakes
- Forgetting
USERentirely. An image with noUSERinstruction runs as root by default. Make this an explicit choice, not an oversight. - Switching to a non-root user, then trying to bind port 80 or 443. Non-root users cannot bind privileged ports without
cap_net_bind_service. Use a port above 1024 (commonly8080) and let your load balancer or ingress map externally. - Using a name that won't exist in production. If you set
USER appbut the runtime image lacks anappentry in/etc/passwd, the container fails to start. Numeric IDs are immune to this. - Mismatched volume ownership. When a host directory or named volume is mounted, its existing UID/GID is preserved. Either set them to match your container UID, run an init container to
chownthem, or use a volume policy that matches.
Pre-flight checklist
- Has a non-root user been created?
- Is the final
USERa numeric UID, not a name? - Does
docker run --rm <image> idreport a UID that is not 0? - Can the application write to every path it expects to write to?
- Are exposed ports above 1024?
Related
- FROM reference — minimal base images that already ship a
nonrootuser. - COPY reference — using
--chownto hand ownership to the target user. - WORKDIR reference — directory creation respects the current
USER. - Securing Docker builds tutorial — non-root containers in context.
- Security best practices for Docker images