| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64 |
| Applies to | Any Linux host with Podman installed |
| Privilege | Rootless examples as podtest; rootful examples with sudo podman |
| Scope | User namespace theory and --userns modes — host, keep-id, nomap, auto, explicit uidmapping, --uidmap/--gidmap, strange host UIDs, pod inheritance, and keep-id vs auto conflicts. Does not cover subuid provisioning, SELinux volume diagnosis, :z/:Z/:U depth, or full podman unshare workflows. |
| Related guides | Podman storage location |
Inside a container, id may report uid=0(root). On the host, a file that container wrote can show 1014, 984038, or another number that does not match what you expected. Linux user namespaces let Podman translate identities:
container UID
↓
namespace / intermediate UID
↓
host UIDThis guide explains how those translations work in Podman 5.8, when to pick each --userns mode, and how to read the mappings on a real rootless lab user. Subordinate range setup belongs in Rootless Podman; volume fixes belong in Fix Podman volume permission denied.
The lab user is podtest (UID 1014) for rootless examples.
Why Podman uses user namespaces
Containers need their own UID 0 for package scripts and image defaults, but mapping every container UID to a real host account would not scale. User namespaces give each container (or Podman session) an isolated ID space while the kernel maps those IDs to host values at the filesystem and process boundary.
Run a default rootless container and read IDs inside it:
podman run --rm docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)Create a file on a bind mount without keep-id, then inspect ownership on the host:
mkdir -p ~/userns-demo/data && chmod 777 ~/userns-demo/dataWrite as container UID 999 through a bind mount:
podman run --rm --user 999 -v ~/userns-demo/data:/data:Z docker.io/library/alpine:3.20 touch /data/exampleFrom the host shell the owner is a subordinate ID, not 999:
ls -ln ~/userns-demo/data/example-rw-r--r--. 1 984038 1015 0 Aug 23 00:56 /home/podtest/userns-demo/data/exampleThe container used UID 999, but the host stores 984038. That gap is the user namespace at work — not a random permission bug.
Rootless Podman already runs inside a user namespace
Rootless Podman never executes as host UID 0. Even when you pass --userns=host, you are not joining the initial host user namespace as real root. Rootless --userns=host does not mean the initial host user namespace or real host root. Podman still operates rootlessly: container UID 0 maps to the UID of the user invoking Podman. The host name means Podman does not create an additional private user namespace for this container beyond the rootless namespace arrangement it uses for the caller.
rootless --userns=host
→ container root is not host rootCompare default and explicit host mode — on this lab host they match:
podman run --rm --userns=host docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)Container uid=0(root) is namespace-local. On the host you remain podtest. Rootless vs rootful Podman covers what changes when you switch to sudo podman.
Inspect Podman's rootless UID and GID mapping
Before changing --userns modes, read the baseline mapping Podman uses for your account. Each /proc/self/uid_map line has three columns:
namespace start | host start | lengthView the rootless mapping:
podman unshare cat /proc/self/uid_map0 1014 1
1 983040 65536GID mapping follows the same three-column layout:
podman unshare cat /proc/self/gid_map0 1015 1
1 983040 65536Interpretation for podtest:
- Namespace UID
0maps to host UID1014(the logged-in user) for one ID. - Namespace UIDs
1through65536map to host subordinate IDs starting at983040.
Those subordinate numbers come from /etc/subuid and /etc/subgid:
grep '^podtest:' /etc/subuid /etc/subgid/etc/subuid:podtest:983040:65536
/etc/subgid:podtest:983040:65536How to allocate ranges is out of scope here — see Rootless Podman.
Default --userns=host
When --userns is omitted, Podman resolves the mode in this order:
- A pod's user namespace wins when the container joins a pod.
PODMAN_USERNScan set a default.containers.confcan set a default.- Otherwise Podman uses
host.
For rootless Podman, host maps the calling user's host UID to container UID 0 unless the image USER or a --user flag changes the process identity.
host user UID 1014 → container UID 0 (default rootless host mode)That is why a plain podman run shows uid=0(root) inside the container while id on the SSH session still shows podtest.
--userns=keep-id
keep-id is the mode you reach for when a rootless container must cooperate with host-owned bind mounts. It maps your current UID and GID to the same numeric IDs inside the container.
Read the host IDs first:
id -u1014The GID is the companion value for group-owned files:
id -g1015Run with keep-id:
podman run --rm --userns=keep-id docker.io/library/alpine:3.20 iduid=1014(podtest) gid=1015(podtest) groups=1015(podtest)On Podman 5.8, keep-id also runs the initial process as the mapped UID by default, overriding the image USER unless you pass --user explicitly. The container no longer starts as UID 0 unless you ask for it.
Write through a bind mount and confirm host ownership stays aligned:
podman run --rm --userns=keep-id -v ~/userns-demo/data:/data:Z docker.io/library/alpine:3.20 sh -c 'touch /data/keepid-file && ls -ln /data/keepid-file'uid=1014(podtest) gid=1015(podtest) groups=1015(podtest)
-rw-r--r-- 1 1014 1015 0 Aug 22 19:26 /data/keepid-fileThe same numeric owner should appear on the host path:
ls -ln ~/userns-demo/data/keepid-file-rw-r--r--. 1 1014 1015 0 Aug 23 00:56 /home/podtest/userns-demo/data/keepid-fileThe uid_map for keep-id differs from the default rootless layout:
podman run --rm --userns=keep-id docker.io/library/alpine:3.20 cat /proc/self/uid_map0 1 1014
1014 0 1
1015 1015 64522Host 1014 maps to container 1014, and the remaining subordinate span is still available below the keep-id mapping block.
Map the current user to a different container UID or GID
Some images expect a fixed application UID such as 999. Remap your host user into that container identity:
podman run --rm --userns=keep-id:uid=999,gid=999 docker.io/library/alpine:3.20 iduid=999(yggdrasil) gid=999(ping) groups=999(ping)host current user (1014)
↓
container UID 999 / GID 999You still get keep-id semantics — the mapping is anchored to your account — but the in-container numbers match what the image expects. Volume permission trees and SELinux relabel workflows are documented separately; this section only shows the namespace choice.
Limit keep-id namespace size
An ordinary keep-id container can consume the entire subordinate range allocated to your user. Podman 5.8 supports size= on rootless keep-id to cap that footprint:
podman run --rm --userns=keep-id:size=8192 docker.io/library/alpine:3.20 iduid=1014(podtest) gid=1015(podtest) groups=1015(podtest)The capped mapping leaves headroom for other containers:
podman run --rm --userns=keep-id:size=8192 docker.io/library/alpine:3.20 cat /proc/self/uid_map0 1 1014
1014 0 1
1015 1015 7177keep-id:size= is a rootless feature. Root-created keep-id does not behave the same way on this Podman version — keep sizing examples on an unprivileged account.
--userns=nomap
nomap builds a user namespace from your subordinate IDs while deliberately excluding the caller's own host UID and GID from the container namespace.
podman run --rm --userns=nomap docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)Inside the container you still see uid=0(root), but the mapping no longer includes your host account:
podman run --rm --userns=nomap docker.io/library/alpine:3.20 cat /proc/self/uid_map0 1 65536Contrast:
keep-id → caller's own UID is intentionally mapped into the container
nomap → caller's own UID is intentionally absentUse nomap when the workload should not carry an identity tied to your login. Root-created containers cannot use rootless nomap semantics.
--userns=auto
auto allocates a fresh, automatically sized user namespace from available subordinate IDs. Each container can receive its own shifted range — useful when many containers should not share the same mapping.
podman run --rm --userns=auto docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)Inspect the namespace Podman picked:
podman run --rm --userns=auto docker.io/library/alpine:3.20 cat /proc/self/uid_map0 1 1024Podman estimated 1024 IDs for this image on the lab host. Without an explicit size, that estimate depends on image metadata and available subordinate space.
Control auto namespace size
Cap the range when you need many independent auto containers:
podman run --rm --userns=auto:size=8192 docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)Pick a size large enough for every UID the image uses. An artificially tiny range breaks images that expect high numeric service accounts.
Add explicit mappings to auto
Advanced auto options accept explicit translation rules:
uidmapping=CONTAINER_UID:HOST_UID:SIZE
gidmapping=CONTAINER_GID:HOST_GID:SIZEPodman 5.8 also supports @ to reference a host ID without hand-calculating the intermediate rootless namespace offset.
Run one container UID through a fixed mapping:
podman run --rm --userns=auto:uidmapping=1000:1:1,gidmapping=1000:1:1 --user 1000 docker.io/library/alpine:3.20 sh -c 'id; cat /proc/self/uid_map'uid=1000(golinuxcloud) gid=0(root) groups=0(root)
0 2 1000
1001 1002 23
1000 1 1Container UID 1000 maps through the auto namespace using the 1000:1:1 rule. Treat explicit uidmapping as an advanced knob — verify the resulting uid_map on your host before relying on it in production.
keep-id vs auto vs nomap
| Mode | Current user's UID mapped? | Main use |
|---|---|---|
host |
caller maps to container root in normal rootless default | simple default rootless behavior |
keep-id |
Yes — same or specified container ID | host-owned bind mounts |
nomap |
No | isolate container IDs from caller |
auto |
No direct caller mapping | unique ranges per container |
Decision guide:
Need host-user-owned bind mounts?
→ keep-id
Need independently shifted ranges per container?
→ auto
Want caller UID deliberately absent?
→ nomap
No special mapping need?
→ default host modeWhy keep-id can conflict with auto
keep-id and nomap can each consume the user's full subordinate allocation. While such a container is running, Podman may not have free IDs for a new --userns=auto workload.
Reproduce the failure:
podman run -d --name keepid-holder --userns=keep-id docker.io/library/alpine:3.20 sleep 600With that container still running, a new auto workload fails:
podman run --rm --userns=auto docker.io/library/alpine:3.20 idError: creating container storage: not enough unused IDs in user namespaceRemove the blocker and auto works again:
podman rm -f keepid-holderAfter the holder is gone, auto succeeds again:
podman run --rm --userns=auto docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)With a sized keep-id container, auto can coexist:
podman run -d --name keepid-small --userns=keep-id:size=8192 docker.io/library/alpine:3.20 sleep 600A limited keep-id footprint leaves room for auto:
podman run --rm --userns=auto docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)Remove the sized holder when you are done:
podman rm -f keepid-smallWhen you need long-lived keep-id services and separate auto containers, use keep-id:size= instead of an unlimited keep-id footprint. The same exhaustion pattern appears with a running nomap container — it also reserves the full subordinate span, so remove or resize blocking containers before expecting auto to allocate a fresh range.
Rootful --userns=auto is different
Rootful auto does not read the root user's personal /etc/subuid entry the way rootless auto does. Rootful --userns=auto normally draws from subordinate IDs reserved for the containers account. containers/storage also exposes root-auto-userns-user under [storage.options] when administrators need to select a different account whose subordinate ranges should supply automatic mappings. This setting is ignored for rootless Podman.
Example configuration when you need a non-default account:
[storage.options]
root-auto-userns-user = "containers"On a fresh host the containers account may have no range yet:
grep '^containers:' /etc/subuid /etc/subgidWhen no line is returned, rootful auto fails:
sudo podman run --rm --userns=auto docker.io/library/alpine:3.20 idError: creating container storage: not enough unused IDs in user namespaceCheck the detailed cause in the same run — Podman names the missing containers mapping:
Cannot find mappings for user "containers": no subuid ranges found for user "containers" in /etc/subuidConfigure the rootful containers mapping
For rootful auto testing, assign a non-overlapping subordinate range to the containers account. Use IDs that do not collide with real users on the host.
echo 'containers:200000:65536' | sudo tee -a /etc/subuidMirror the same range for groups:
echo 'containers:200000:65536' | sudo tee -a /etc/subgidVerify:
getsubids containers0: containers 200000 65536Rerun rootful auto:
sudo podman run --rm --userns=auto docker.io/library/alpine:3.20 sh -c 'id; cat /proc/self/uid_map'uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
0 200000 1024Rootful auto mapped container UID 0 starting at host 200000 — a different source pool than rootless podtest uses. Production hosts may ship this mapping preconfigured; the lab added it to demonstrate the failure and fix.
Join another container's user namespace
Podman can reuse an existing container namespace with container:NAME:
podman run -d --name userns-source --userns=auto docker.io/library/alpine:3.20 sleep 600Read the source container's mapping before joining it:
podman exec userns-source cat /proc/self/uid_map0 1 1024Join that namespace from a second container:
podman run --rm --userns=container:userns-source docker.io/library/alpine:3.20 cat /proc/self/uid_map0 1 1024Both containers share the same mapping — useful for tightly coupled sidecars. Clean up when finished:
podman rm -f userns-sourceYou can also join an existing host namespace by path (--userns=ns:/proc/PID/ns/user) when you already know the target PID. That path is advanced and brittle across restarts; prefer container:NAME for Podman-managed workloads.
Custom mapping with --uidmap and --gidmap
Explicit maps use:
--uidmap CONTAINER_UID:HOST_UID:COUNT
--gidmap CONTAINER_GID:HOST_GID:COUNTExplicit --uidmap / --gidmap mappings are alternatives to --userns; do not combine the two approaches on the same container. They also cannot be set on an individual container joining a pod — configure the user namespace at pod creation instead.
Rootful interpretation is direct — host IDs map into the container namespace:
sudo podman run --rm --uidmap 0:1000:1 --gidmap 0:1000:1 docker.io/library/alpine:3.20 sh -c 'id; cat /proc/self/uid_map'uid=0(root) gid=0(root) groups=0(root)
0 1000 1Host UID 1000 becomes container UID 0.
Rootless mapping is two-step. Your host UID first enters Podman's intermediate rootless namespace (the podman unshare view), then --uidmap rules apply inside the per-container namespace. Hand-tuning rootless maps is easy to get wrong; prefer keep-id, auto, or @ unless you have a concrete uid_map to match.
A minimal rootless @ example maps container UID 0 through the caller's host identity:
podman run --rm --uidmap 0:@1014:1 --gidmap 0:@1015:1 docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root)@1014 tells Podman to resolve host UID 1014 into the correct intermediate ID instead of forcing you to compute it from /proc/self/uid_map by hand.
Why files have strange UIDs on the host
This is the question most readers arrive with. A container process uses UID 999, but the host stores a subordinate ID from your allocation.
Inside Podman's user namespace the same file looks like UID 999:
podman unshare ls -ln ~/userns-demo/data/example-rw-r--r--. 1 999 0 0 Aug 23 00:56 /home/podtest/userns-demo/data/exampleFrom an ordinary shell the host kernel reports the mapped value:
ls -ln ~/userns-demo/data/example-rw-r--r--. 1 984038 1015 0 Aug 23 00:56 /home/podtest/userns-demo/data/exampleMath on this host: subordinate range starts at 983040, and default rootless mapping places container UID 999 at 983040 + 998 = 984038. The behavior is expected namespace translation — not proof that Podman picked the wrong user. Switch to keep-id when the file must remain owned by your host UID; see Fix Podman volume permission denied when access still fails after you understand the mapping.
--user vs --userns
The flags solve different problems:
--user → which UID/GID the process uses inside the container
--userns → how container UID/GID values map outside the containerYou can combine them when an image expects UID 999 and you also want keep-id alignment:
podman run --rm --user 999 --userns=keep-id:uid=999,gid=999 docker.io/library/alpine:3.20 iduid=999(yggdrasil) gid=999(ping) groups=999(ping)--user selects the in-container identity. --userns defines how that identity appears on host mounts and in /proc/.../uid_map.
Pods and --userns
When a container joins a pod with an infra container, the pod determines the user namespace. Do not try to assign a separate user namespace to an individual member; on the tested Podman 5.8.2 host, explicitly combining --pod with a conflicting --userns is rejected.
Create a pod with auto and run a member without a separate namespace flag:
podman pod create --name userns-pod --userns=autoRun a pod member without its own --userns flag:
podman run --rm --pod userns-pod docker.io/library/alpine:3.20 sh -c 'id; cat /proc/self/uid_map'uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
0 1 1024Trying to override the pod namespace fails:
podman run --rm --pod userns-pod --userns=keep-id docker.io/library/alpine:3.20 idError: cannot set user namespace mode when joining pod with infra container: invalid argumentPlan user namespace mode at podman pod create time. Pod networking and shared namespaces are covered in Podman pods.
podman pod rm -f userns-podReferences
- Podman run manual — user namespace options
- Podman rootless documentation
- User namespaces — man7.org
- subuid — man page
- containers-storage.conf — root-auto-userns-user
Summary
Podman translates container UIDs and GIDs through Linux user namespaces. Rootless Podman always operates inside that model — even --userns=host maps your login to namespace-local root rather than host root. Reading podman unshare cat /proc/self/uid_map connects the abstract diagram to the subordinate ranges in /etc/subuid.
For day-to-day rootless work, keep-id keeps bind mounts aligned with your host account, auto gives each container its own shifted range, and nomap deliberately omits your login from the container identity. The default host mode is fine when you do not need special file ownership behavior. Watch for keep-id or nomap containers that exhaust subordinate IDs and block auto; keep-id:size= leaves room for additional namespaces.
Rootful auto is a separate path: it normally consumes subordinate IDs reserved for the containers account rather than the invoking root user's ranges; root-auto-userns-user can select a different account. Strange numeric owners on host files are usually mapped subordinate IDs — use podman unshare ls -ln to see the in-container view, then pick keep-id or the volume permission fixes when you need host-owned paths to line up.

