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:

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.

Scope and ordering

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:

  1. Use COPY --chown for application files. A single COPY --chown=app:app ./build /app hands ownership over at copy time without an extra layer.
  2. Set WORKDIR after USER if the directory may not exist yet. A WORKDIR creates missing directories owned by the current user, so when it runs after USER it 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

Pre-flight checklist

  1. Has a non-root user been created?
  2. Is the final USER a numeric UID, not a name?
  3. Does docker run --rm <image> id report a UID that is not 0?
  4. Can the application write to every path it expects to write to?
  5. Are exposed ports above 1024?

Related