Chapter 27Lesson 01~125 minutes

User Namespace Remapping, UID/GID Mapping, Non-Root Images, Filesystem Ownership, and Least Privilege: Concepts, Architecture, and Mental Model

Trace identity from Dockerfile USER and runtime --user through container UID/GID, optional user namespaces, subordinate host IDs, and filesystem permission checks.

UID/GIDUSERUser namespacesFilesystem ownershipLeast privilege

Learning objectives

  • Explain the identity chain from image USER through runtime overrides, container numeric UID/GID, optional user namespaces, and host filesystem checks.
  • Distinguish names such as app from the numeric IDs that Linux permission checks actually use.
  • Compare ordinary rootful, userns-remap, and rootless UID/GID mapping without conflating their daemon privilege models.
  • Inspect image, container, mount, subordinate-range, and ownership state before attempting any permission change.
Chapter 27 principle. “Non-root” is only meaningful when the numeric identity, namespace mapping, writable paths, and host/storage ownership are all observable and intentional.

1. The practical problem: “non-root” is not one identity

Dockerfiles, containers, user namespaces, and host files all use the word “user,” but Linux ultimately authorizes filesystem access with numeric UIDs, GIDs, supplementary groups, capabilities, ACLs, and security policy. A container may display the username app while the host sees only UID 10001—or a remapped high-number UID such as 231073.

The operational goal is therefore not “make the prompt stop saying permission denied.” It is to establish a traceable identity contract: which numeric identity the process has, how that identity maps to the host, which path it must write, and which paths it must not write.

2. Mental model: image identity → runtime identity → optional namespace translation → inode check

USER in a Dockerfile writes a default user into the image config. docker run --user or Compose user: can override it at runtime. The resulting process has numeric UID/GID values inside its container namespace. If user namespaces are disabled, those numeric IDs exist directly in the initial host user namespace. If userns-remap or rootless mode is active, Linux translates them into other host IDs before the kernel evaluates host filesystem ownership.

A bind mount does not magically “belong to the container.” The underlying host inode still has an owner, group, mode bits, optional ACLs, and possibly SELinux/AppArmor policy. Those checks happen after any namespace translation.

Identity path from image metadata to host filesystem checks
flowchart TD
  A[Dockerfile USER / image config] --> B[Runtime --user / Compose user override]
  B --> C[Container numeric UID:GID + supplementary groups]
  C --> D{User namespace?}
  D -->|No| E[Same numeric IDs in initial host user namespace]
  D -->|userns-remap| F[Container IDs translated through remap subuid/subgid]
  D -->|rootless| G[UID 0 -> host user; other IDs -> subordinate range]
  E --> H[Bind/volume inode ownership + mode/ACL/LSM checks]
  F --> H
  G --> H
  H --> I[Read/write allowed or denied]
            

3. Names are labels; numeric IDs are the durable evidence

Layer Useful evidence Common mistake
Image config docker image inspect ... .Config.User Assuming a username has the same numeric ID in every image/host.
Container process id, /proc/self/status Looking only at the shell prompt or username string.
Runtime override docker inspect ... .Config.User Forgetting --user/Compose can override image USER.
Supplementary groups id -G, --group-add evidence Checking UID/GID but ignoring shared group permissions.
User namespace /proc/self/uid_map, gid_map Calling root inside a namespace equivalent to host root.
Host path stat -c %u:%g:%a Changing mode before proving the mapped numeric owner.
Volume/bind metadata docker inspect mounts Confusing Docker object ownership with filesystem inode ownership.

4. Three mapping models you must keep separate

Ordinary rootful daemon without userns-remap: container UID 10001 is host UID 10001 for host filesystem checks.

Rootful daemon with userns-remap: container UID 0 maps to the first subordinate UID assigned to the remap user; container UID n maps to subordinate-start + n. The daemon itself still runs as root.

Rootless Docker: container UID 0 maps to the host UID of the user running rootless Docker. Container IDs ≥1 map into that user’s subordinate range using Docker’s documented offset rule. Both daemon and containers run without host-root privilege.

5. Read-only baseline inspection

docker version
docker context show
docker info --format 'Security={{json .SecurityOptions}} Root={{.DockerRootDir}}'

# Image configured identity (read-only)
docker image inspect alpine:3.22.1 --format 'User={{json .Config.User}}' 2>/dev/null || true

# Host identity/ranges on Linux
id
cat /etc/subuid 2>/dev/null | head
cat /etc/subgid 2>/dev/null | head

Do not infer daemon mode from a context name. docker info security options plus socket/data-root evidence are stronger proof.

6. Dockerfile USER and COPY --chown are different controls

USER chooses the default identity for later build instructions that honor it and, critically, the default runtime process. COPY --chown sets ownership on files added to the image. They solve different problems: one controls process identity; the other prepares filesystem state.

Docker resolves named --chown values through the image’s /etc/passwd and /etc/group. Numeric IDs avoid name lookup and are useful when you need an explicit cross-image contract.

7. DevOps contract: identity must survive rebuild, replacement, and restore

A reproducible deployment records at least: image digest, configured runtime user, any runtime override, writable paths, volume/bind ownership contract, user-namespace mode, and the expected numeric IDs. If a restored volume suddenly needs root or world-write access, that is evidence that the identity contract was never captured or was changed.

Knowledge check

Why is a username not enough evidence for filesystem access?

What does Dockerfile USER control?

How does userns-remap map container UID 0?

How does rootless mapping differ for container UID 0?

Why does a bind mount often reveal identity mistakes?

Next lesson

Next: User Namespace Remapping, UID/GID Mapping, Non-Root Images, Filesystem Ownership, and Least Privilege: Guided Hands-On Workflow and Core Operations

Continue with the next lesson in the course sequence and carry forward the evidence-first Docker operating model.

Official references and version notes

  • User namespace remapping — current prerequisites, mapping behavior, daemon configuration, limitations, and migration cautions.
  • Rootless UID/GID mapping — precise difference between userns-remap and rootless host-ID translation.
  • Rootless mode — daemon-level non-root execution and subordinate-ID prerequisites.
  • Dockerfile reference — USER and COPY --chown semantics, including numeric versus name lookup.
  • Compose services reference — service user override and user namespace configuration surface.
  • Bind mounts — host-path ownership, read/write authority, and daemon-host path semantics.
  • Volumes — Docker-managed persistent storage and container mount behavior.
  • Docker Engine 29 release notes — current Engine baseline and user-namespace-related fixes/changes.
Version/platform baseline, verified 2026-09-22.

Docker Engine 29.8.1 is the current Engine release. ' Docker documents two different mappings: in userns-remap, container UID/GID 0 maps to the first subordinate ID and ID n maps to subordinate-start + n; in rootless mode, container UID/GID 0 maps to the host user and IDs ≥1 map into the subordinate range with an offset. ' Docker recommends enabling userns-remap on a new daemon because existing Docker objects become masked by the remapped storage layout, and it warns that host-mounted filesystem ownership must be arranged for the mapped IDs. ' COPY --chown accepts names or numeric IDs; names require matching /etc/passwd//etc/group inside the build rootfs, while numeric IDs do not. ' Compose user overrides the image-configured user. Every lab therefore records the actual Engine/context/security mode and numeric UID/GID evidence instead of assuming usernames imply equivalent authority.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.