Linux User Namespaces and UID/GID Mapping
Linux user namespaces let a process have its own view of user and group IDs, along with a namespace-scoped set of privileges. A process can therefore be UID 0—traditionally called root—inside a container while corresponding to an ordinary, non-root UID on the host.
This is the mechanism behind many rootless containers. It separates what identity a process appears to have inside its environment from what identity the host kernel uses to protect host resources.
The problem without user namespaces
A Linux process has user and group IDs. The kernel uses them when checking whether the process may read a file, change a network setting, send a signal, mount something, or perform another privileged operation.
Before user namespaces were commonly used, a container process running as root usually had UID 0 on the host too. Other container isolation mechanisms could hide processes, mounts, or network interfaces, but the kernel still saw the process's identity as host root. A container escape or a mistake in the container runtime could therefore turn a container-root operation into a host-root operation.
Running the process as a normal host user avoids that problem, but breaks software that expects to be installed or configured by root. Package managers, service setup, file ownership, and applications that check for UID 0 may all behave differently.
A user namespace addresses both sides: the process can be root in its namespace, while the host treats it as an ordinary user.
One process, two UID values
A user namespace defines mappings between IDs visible inside the namespace and IDs in the parent namespace. A common rootless-container mapping looks conceptually like this:
| Container ID | Host ID |
|---|---|
| 0 | 100000 |
| 1 | 100001 |
| 2 | 100002 |
| ... | ... |
Inside the container, UID 0 is root. On the host, that same process is UID 100000, which might be an allocated subordinate ID belonging to the user who started the container. The host does not treat UID 100000 as root.
The mapping can contain ranges rather than one entry per ID. For example, an entry equivalent to 0 100000 65536 says: map 65,536 IDs starting at container ID 0 to 65,536 IDs starting at host ID 100000.
The kernel stores the mapping for a namespace. It exposes the UID mapping through a file such as /proc/<pid>/uid_map; the analogous GID mapping is in gid_map. These are kernel interfaces, not ordinary configuration files that any process can freely edit. The process creating a namespace, or a suitably privileged process, must establish the mapping under rules enforced by the kernel.
What the kernel does with the mapping
Suppose a process inside the namespace creates a file while running as UID 0. When the filesystem records the file's owner, the kernel translates that namespace UID through the mapping. With the mapping above, the host sees the file as owned by UID 100000, not UID 0.
The reverse translation applies when the process examines an object. A file owned by host UID 100000 can appear inside the namespace as owned by UID 0. A file owned by host root, UID 0, does not automatically become owned by container root when container root maps to host UID 100000.
This is why a command can print apparently contradictory results:
# inside the container
$ id
uid=0(root) gid=0(root)
# from the host, for the same process
$ ps -o uid,pid,cmd
UID PID CMD
100000 ... ...
The /etc/passwd file may provide the name root, but that name is only a user-space presentation. The kernel's permission checks use the namespace-aware numeric IDs and the process's capabilities.
GIDs and group access
Groups work the same way. A process may have GID 0 inside the namespace while that ID maps to a non-root host GID. Supplementary groups—additional group IDs used for access checks—also need to be handled when a container is created.
There is an extra safety rule around group mappings: changing supplementary groups can grant access to files that the process could not otherwise use. For that reason, an unprivileged namespace setup commonly writes a deny value to the namespace's setgroups control before writing gid_map, unless the creator has the required host-side authority. This ordering is a frequent source of container-startup errors.
Root still has limits
UID 0 inside a user namespace is not host root. The process receives capabilities associated with that namespace. A capability is a separate kernel-recognized permission, such as changing file ownership or configuring certain network features, that can be granted without making a process globally omnipotent.
A newly created namespace can give its namespace-root process many capabilities within that namespace. Those capabilities do not authorize arbitrary operations on host-wide resources. For example, container root generally cannot:
- read files owned by host root merely because it is UID 0 inside the container;
- change host-global network configuration;
- signal arbitrary host processes;
- load a host kernel module; or
- bypass ordinary host filesystem permissions for an unmapped ID.
Other container mechanisms—such as mount, process, network, and cgroup isolation—are usually combined with user namespaces. User namespaces protect the identity boundary; they do not by themselves hide processes or provide a complete container.
How this appears in practice
When a runtime reports an error involving uid_map, gid_map, subuid, subgid, or setgroups, it is describing this setup. Files such as /etc/subuid and /etc/subgid commonly allocate ranges of host IDs that an unprivileged user may map into a user namespace. A rootless runtime uses those ranges so that container IDs have safe host-side identities.
The essential rule is simple: UID 0 is meaningful only relative to a user namespace. UID/GID mappings let software preserve the familiar root-based environment inside a container while ensuring that the host sees the process—and the files it creates—as an ordinary, explicitly mapped identity.