Podman User Namespaces and UID/GID Mapping Explained

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:

text
container UID
namespace / intermediate UID
host UID

This 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:

bash
podman run --rm docker.io/library/alpine:3.20 id
output
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)

Create a file on a bind mount without keep-id, then inspect ownership on the host:

bash
mkdir -p ~/userns-demo/data && chmod 777 ~/userns-demo/data

Write as container UID 999 through a bind mount:

bash
podman run --rm --user 999 -v ~/userns-demo/data:/data:Z docker.io/library/alpine:3.20 touch /data/example

From the host shell the owner is a subordinate ID, not 999:

bash
ls -ln ~/userns-demo/data/example
output
-rw-r--r--. 1 984038 1015 0 Aug 23 00:56 /home/podtest/userns-demo/data/example

The 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.

text
rootless --userns=host
    → container root is not host root

Compare default and explicit host mode — on this lab host they match:

bash
podman run --rm --userns=host docker.io/library/alpine:3.20 id
output
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)

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:

text
namespace start | host start | length

View the rootless mapping:

bash
podman unshare cat /proc/self/uid_map
output
0       1014          1
         1     983040      65536

GID mapping follows the same three-column layout:

bash
podman unshare cat /proc/self/gid_map
output
0       1015          1
         1     983040      65536

Interpretation for podtest:

  • Namespace UID 0 maps to host UID 1014 (the logged-in user) for one ID.
  • Namespace UIDs 1 through 65536 map to host subordinate IDs starting at 983040.

Those subordinate numbers come from /etc/subuid and /etc/subgid:

bash
grep '^podtest:' /etc/subuid /etc/subgid
output
/etc/subuid:podtest:983040:65536
/etc/subgid:podtest:983040:65536

How 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:

  1. A pod's user namespace wins when the container joins a pod.
  2. PODMAN_USERNS can set a default.
  3. containers.conf can set a default.
  4. 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.

text
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:

bash
id -u
output
1014

The GID is the companion value for group-owned files:

bash
id -g
output
1015

Run with keep-id:

bash
podman run --rm --userns=keep-id docker.io/library/alpine:3.20 id
output
uid=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:

bash
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'
output
uid=1014(podtest) gid=1015(podtest) groups=1015(podtest)
-rw-r--r--    1 1014     1015             0 Aug 22 19:26 /data/keepid-file

The same numeric owner should appear on the host path:

bash
ls -ln ~/userns-demo/data/keepid-file
output
-rw-r--r--. 1 1014 1015 0 Aug 23 00:56 /home/podtest/userns-demo/data/keepid-file

The uid_map for keep-id differs from the default rootless layout:

bash
podman run --rm --userns=keep-id docker.io/library/alpine:3.20 cat /proc/self/uid_map
output
0          1       1014
      1014          0          1
      1015       1015      64522

Host 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:

bash
podman run --rm --userns=keep-id:uid=999,gid=999 docker.io/library/alpine:3.20 id
output
uid=999(yggdrasil) gid=999(ping) groups=999(ping)
text
host current user (1014)
container UID 999 / GID 999

You 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:

bash
podman run --rm --userns=keep-id:size=8192 docker.io/library/alpine:3.20 id
output
uid=1014(podtest) gid=1015(podtest) groups=1015(podtest)

The capped mapping leaves headroom for other containers:

bash
podman run --rm --userns=keep-id:size=8192 docker.io/library/alpine:3.20 cat /proc/self/uid_map
output
0          1       1014
      1014          0          1
      1015       1015       7177

keep-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.

bash
podman run --rm --userns=nomap docker.io/library/alpine:3.20 id
output
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)

Inside the container you still see uid=0(root), but the mapping no longer includes your host account:

bash
podman run --rm --userns=nomap docker.io/library/alpine:3.20 cat /proc/self/uid_map
output
0          1      65536

Contrast:

text
keep-id  → caller's own UID is intentionally mapped into the container
nomap    → caller's own UID is intentionally absent

Use 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.

bash
podman run --rm --userns=auto docker.io/library/alpine:3.20 id
output
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)

Inspect the namespace Podman picked:

bash
podman run --rm --userns=auto docker.io/library/alpine:3.20 cat /proc/self/uid_map
output
0          1       1024

Podman 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:

bash
podman run --rm --userns=auto:size=8192 docker.io/library/alpine:3.20 id
output
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)

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:

text
uidmapping=CONTAINER_UID:HOST_UID:SIZE
gidmapping=CONTAINER_GID:HOST_GID:SIZE

Podman 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:

bash
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'
output
uid=1000(golinuxcloud) gid=0(root) groups=0(root)
         0          2       1000
      1001       1002         23
      1000          1          1

Container 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:

text
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 mode

Why 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:

bash
podman run -d --name keepid-holder --userns=keep-id docker.io/library/alpine:3.20 sleep 600

With that container still running, a new auto workload fails:

bash
podman run --rm --userns=auto docker.io/library/alpine:3.20 id
output
Error: creating container storage: not enough unused IDs in user namespace

Remove the blocker and auto works again:

bash
podman rm -f keepid-holder

After the holder is gone, auto succeeds again:

bash
podman run --rm --userns=auto docker.io/library/alpine:3.20 id
output
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)

With a sized keep-id container, auto can coexist:

bash
podman run -d --name keepid-small --userns=keep-id:size=8192 docker.io/library/alpine:3.20 sleep 600

A limited keep-id footprint leaves room for auto:

bash
podman run --rm --userns=auto docker.io/library/alpine:3.20 id
output
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)

Remove the sized holder when you are done:

bash
podman rm -f keepid-small

When 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:

text
[storage.options]
root-auto-userns-user = "containers"

On a fresh host the containers account may have no range yet:

bash
grep '^containers:' /etc/subuid /etc/subgid

When no line is returned, rootful auto fails:

bash
sudo podman run --rm --userns=auto docker.io/library/alpine:3.20 id
output
Error: creating container storage: not enough unused IDs in user namespace

Check the detailed cause in the same run — Podman names the missing containers mapping:

text
Cannot find mappings for user "containers": no subuid ranges found for user "containers" in /etc/subuid

Configure 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.

bash
echo 'containers:200000:65536' | sudo tee -a /etc/subuid

Mirror the same range for groups:

bash
echo 'containers:200000:65536' | sudo tee -a /etc/subgid

Verify:

bash
getsubids containers
output
0: containers 200000 65536

Rerun rootful auto:

bash
sudo podman run --rm --userns=auto docker.io/library/alpine:3.20 sh -c 'id; cat /proc/self/uid_map'
output
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       1024

Rootful 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:

bash
podman run -d --name userns-source --userns=auto docker.io/library/alpine:3.20 sleep 600

Read the source container's mapping before joining it:

bash
podman exec userns-source cat /proc/self/uid_map
output
0          1       1024

Join that namespace from a second container:

bash
podman run --rm --userns=container:userns-source docker.io/library/alpine:3.20 cat /proc/self/uid_map
output
0          1       1024

Both containers share the same mapping — useful for tightly coupled sidecars. Clean up when finished:

bash
podman rm -f userns-source

You 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:

text
--uidmap CONTAINER_UID:HOST_UID:COUNT
--gidmap CONTAINER_GID:HOST_GID:COUNT

Explicit --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:

bash
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'
output
uid=0(root) gid=0(root) groups=0(root)
         0       1000          1

Host 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:

bash
podman run --rm --uidmap 0:@1014:1 --gidmap 0:@1015:1 docker.io/library/alpine:3.20 id
output
uid=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:

bash
podman unshare ls -ln ~/userns-demo/data/example
output
-rw-r--r--. 1 999 0 0 Aug 23 00:56 /home/podtest/userns-demo/data/example

From an ordinary shell the host kernel reports the mapped value:

bash
ls -ln ~/userns-demo/data/example
output
-rw-r--r--. 1 984038 1015 0 Aug 23 00:56 /home/podtest/userns-demo/data/example

Math 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:

text
--user   → which UID/GID the process uses inside the container
--userns → how container UID/GID values map outside the container

You can combine them when an image expects UID 999 and you also want keep-id alignment:

bash
podman run --rm --user 999 --userns=keep-id:uid=999,gid=999 docker.io/library/alpine:3.20 id
output
uid=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:

bash
podman pod create --name userns-pod --userns=auto

Run a pod member without its own --userns flag:

bash
podman run --rm --pod userns-pod docker.io/library/alpine:3.20 sh -c 'id; cat /proc/self/uid_map'
output
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       1024

Trying to override the pod namespace fails:

bash
podman run --rm --pod userns-pod --userns=keep-id docker.io/library/alpine:3.20 id
output
Error: cannot set user namespace mode when joining pod with infra container: invalid argument

Plan user namespace mode at podman pod create time. Pod networking and shared namespaces are covered in Podman pods.

bash
podman pod rm -f userns-pod

References


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.


Frequently Asked Questions

1. What is the default --userns mode in rootless Podman?

When you do not set --userns, Podman falls back to host mode after checking pod membership, PODMAN_USERNS, and containers.conf. For rootless Podman, host means the caller's host UID maps to container UID 0 inside Podman's existing rootless user namespace — not host root on the machine.

2. When should I use --userns=keep-id?

Use keep-id when a rootless container must read and write bind mounts owned by your host user account. keep-id maps your current UID and GID to the same numeric IDs inside the container, so files created in the mount look owned by you on the host.

3. Why does --userns=auto fail with not enough unused IDs?

A running keep-id or nomap container may already consume the user's full subordinate ID range from /etc/subuid. Podman then has no free IDs left for a new auto namespace. Remove the conflicting container or recreate keep-id with keep-id:size= to reserve part of the range.

4. Is rootful --userns=auto the same as rootless auto?

No. Rootless auto draws from the calling user's subordinate UID/GID ranges. Rootful auto normally uses subordinate IDs reserved for the containers account. The root-auto-userns-user setting in storage.conf can select a different account for rootful automatic mappings and is ignored by rootless Podman.

5. Why do bind-mount files show strange numeric UIDs on the host?

Rootless Podman maps container UIDs through subordinate ranges. Container UID 999 may appear as 984038 on the host because the kernel stores the mapped host ID, not the in-container number. podman unshare shows the ID as the container namespace sees it.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)