Fix Podman Volume Permission Denied: SELinux, UID Mapping and Rootless Containers

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; SELinux examples on RHEL, Rocky Linux, AlmaLinux, and Fedora
Privilege Rootful and rootless examples; semanage, restorecon, and ausearch need sudo on SELinux hosts
Scope Diagnosing and fixing permission denied on Podman bind mounts and volumes — Unix ownership, rootless UID and GID mapping, SELinux :z/:Z/:U, keep-id, podman unshare chown, idmapped mounts, persistent file contexts, supplementary groups, and NFS or xattr limits. Does not cover volume create or backup tutorials, full user-namespace theory, or disabling SELinux.
Related guides Rootless Podman

A container that cannot write its mounted data prints a generic Permission denied. The fix depends on which layer blocks access:

  • Unix owner and mode
  • rootless UID mapping
  • SELinux type
  • underlying filesystem limits (xattr, NFS, remote exports)

This guide reproduces real failures, shows the checks that separate those causes, and applies the matching fix only after you know which branch you are on.


Podman volume permission denied: quick diagnostic

Start with evidence, not mount flags. The table below maps what you see to the likely layer.

Check If it fails Likely problem
ls -ld / normal ownership process UID cannot write Unix ownership or mode
host UID looks strange under rootless mapped IDs differ user namespace
Unix permissions look correct but SELinux enforcing AVC or wrong context SELinux
relabel or chown fails on underlying filesystem xattr or userns limitation filesystem
only supplementary-group access exists rootless container cannot see group group mapping

Run these read-only checks before changing ownership or adding :z, :Z, or :U:

Confirm whether SELinux can be involved:

bash
getenforce

Sample output:

output
Enforcing

Inspect Unix permissions on the host path (replace HOST_PATH with your directory):

bash
ls -ld HOST_PATH

Read the SELinux label on the same path:

bash
ls -Zd HOST_PATH

Inspect how the container is configured to run:

bash
podman inspect CONTAINER --format 'User={{.Config.User}}'

For rootless Podman, compare host IDs with IDs inside Podman's user namespace:

bash
podman unshare ls -ln HOST_PATH

On an enforcing host, admin_home_t or var_t on a bind-mounted path often means SELinux — even when chmod 777 would look sufficient on paper.


Diagnose the problem in the right order

Use this decision tree before changing mount options:

text
Container cannot write mounted path
Does normal UID/GID/mode allow the container process?
       ┌────┴────┐
      No         Yes
      │           │
      ▼           ▼
Rootless?      SELinux enforcing?
  │               │
  ▼               ▼
Check UID       Check ls -Z + AVC
mapping            │
  │             Fix :z/:Z or
  │             persistent context
keep-id / :U /
unshare chown
Still fails?
Check underlying filesystem,
xattr support, NFS/userns constraints

The sections below walk each branch with a controlled reproduction on the lab host.


Reproduce Permission denied on a mounted path

Create a host directory you control and mount it without corrective options:

bash
mkdir -p ~/podman-permission-demo

Run a container that tries to create a file under the mount:

bash
podman run --rm -v ~/podman-permission-demo:/data docker.io/library/alpine:3.20 sh -c 'echo test > /data/write-test.txt'

On the lab host with SELinux enforcing, the write fails even though the directory is owned by root and mode 755:

output
sh: can't create /data/write-test.txt: Permission denied

The host path carries admin_home_t, which containers are not allowed to write:

bash
ls -Zd ~/podman-permission-demo
output
unconfined_u:object_r:admin_home_t:s0 /root/podman-permission-demo

Keep ~/podman-permission-demo as the SELinux reproduction throughout this article. A separate path under /srv demonstrates pure Unix ownership failures.


First check the user running inside the container

The container process UID decides which host identity must be allowed to write the mount. Inspect it with:

bash
podman run --rm --user 999:999 docker.io/library/alpine:3.20 id

Sample output:

output
uid=999(yggdrasil) gid=999(ping) groups=999(ping)

Compare that to host ownership in numeric form:

bash
ls -ln /srv/podman-dac-demo

The question is not simply who owns the host directory. Under rootless Podman it is which host UID and GID this container UID maps to. A process that runs as UID 999 inside the container may appear as a subordinate UID such as 100999 on the host, or as a different number after podman unshare ls -ln.


Check whether Podman is rootless

Rootful and rootless setups follow different ownership rules. Ask Podman directly:

bash
podman info --format 'Rootless={{.Host.Security.Rootless}}'

On the lab host while logged in as root, Podman reports rootful mode:

output
Rootless=false

Compare with the login user:

bash
id
output
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Do not assume every permission issue is rootless. Rootful containers can still hit SELinux denials, and rootful podman unshare is not available — that command belongs to the rootless workflow covered later.


Understand rootless UID and GID mapping

Rootless Podman maps container IDs through a user namespace. View the mapping for the logged-in rootless user:

bash
podman unshare cat /proc/self/uid_map

Sample output for user podmantest (UID 1015 on the host):

output
0       1015          1
         1    1048576      65536

The first line means container UID 0 maps to host UID 1015. Container UID 1 and above map into the subuid range starting at 1048576.

Compare host ls -ln with the namespace view of the same path:

bash
ls -ln ~/perm-demo
output
-rw-r--r--. 1 1015 1016 3 Aug 22 23:05 x.txt

Inside Podman's user namespace the same file can appear owned by UID 0:

bash
podman unshare ls -ln ~/perm-demo
output
-rw-r--r--. 1 0 0 3 Aug 22 23:05 x.txt

The application inside the container may see UID 999 while the host stores a subordinate UID. That practical mismatch is why rootless bind mounts fail with correct-looking ls -l on the host. Full mapping modes live in Podman user namespaces.


Fix ownership with podman unshare chown

For rootless Podman, podman unshare chown is the canonical way to set ownership as the container user namespace sees it. Podman translates container UID 999 into the correct host subordinate UID — you do not need to calculate subuid + UID by hand.

Continue with the ~/perm-demo/x.txt file from the mapping section above. It still appears as UID 0 inside podman unshare ls -ln even though the host stores podmantest's login UID. Assign container-visible IDs:

bash
podman unshare chown 999:999 ~/perm-demo/x.txt

Verify the namespace view:

bash
podman unshare ls -ln ~/perm-demo/x.txt
output
-rw-r--r--. 1 999 999 3 Aug 22 23:06 x.txt

On the host Podman stores the mapped subordinate IDs instead:

bash
ls -ln ~/perm-demo/x.txt
output
-rw-r--r--. 1 1049574 1049574 3 Aug 22 23:06 x.txt

You specify container-visible IDs; the user namespace translates them to host subordinate IDs. That two-stage mapping is why podman unshare chown belongs in rootless troubleshooting instead of hand-calculating offsets.

podman unshare only works for the unprivileged user that runs rootless Podman. As root you get Error: please use unshare with rootless.


Use :U to automatically adjust ownership

The U volume option recursively changes owner and group on the host source tree to the host UID and GID that match the container configured user. It modifies real host files — not a virtual in-container mapping.

Before U, the lab file is owned by root:

bash
ls -ln /srv/podman-u-demo/
output
total 4
-rw-r--r--. 1 0 0 7 Aug 22 23:04 file.txt

Run with a non-root container user and both ownership and SELinux relabel on RHEL:

bash
podman run --rm --user 1000:1000 -v /srv/podman-u-demo:/data:U,Z docker.io/library/alpine:3.20 sh -c 'echo after >> /data/file.txt'

After the run, host ownership matches UID 1000:

bash
ls -ln /srv/podman-u-demo/
output
total 4
-rw-r--r--. 1 1000 1000 13 Aug 22 23:04 file.txt

:U alone does not relabel SELinux types. On enforcing hosts you may still need :Z or a persistent container_file_t context before the write succeeds. Treat :U as a host-side ownership change that can break other host applications sharing the same directory.


Use --userns=keep-id

keep-id maps the calling host user's UID and GID into the container so processes can access files owned by that host user without recursive chown.

As root, keep-id still shows UID 0 because you started Podman as root:

bash
podman run --rm --userns=keep-id docker.io/library/alpine:3.20 id
output
uid=0(root) gid=0(root) groups=0(root)

As rootless user podmantest, the same flag maps the real login identity:

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

keep-id does not fix every image automatically — the application may expect a different UID such as 999. Combine it with :z or :Z when SELinux still blocks the mount:

bash
podman run --rm --userns=keep-id -v ~/perm-demo:/data:z docker.io/library/alpine:3.20 sh -c 'cat /data/x.txt'

Configurable forms such as --userns=keep-id:uid=...,gid=... are documented in Podman user namespaces.


Use an idmapped mount where appropriate

Idmapped mounts change the UID/GID view presented through a bind mount without recursively changing ownership on disk. Unlike :U, the backing files retain their original host ownership. Podman supports custom mappings through idmap=uids=...;gids=....

This is an advanced rootful option. Use it when you need a remapped ownership view through the mount — for example so a container user can access host-owned content without recursive chown on the source tree. The exact triplet direction is easy to get wrong; validate mappings on a disposable path before production use.

Example syntax (rootful only):

bash
podman run --rm \
  --mount 'type=bind,src=/srv/podman-idmap-demo,dst=/data,idmap=uids=0-1000-1;gids=0-1000-1' \
  docker.io/library/alpine:3.20 ls -ln /data

Conceptual difference:

text
:U     → recursively chowns host files
idmap  → changes how IDs are presented through the mount

Podman cannot create idmapped mounts as an unprivileged rootless user. Do not present idmap as the rootless replacement for :U.


Reproduce a Unix ownership denial

SELinux and DAC can both print Permission denied. Isolate DAC with a path that already carries container_file_t but wrong Unix ownership.

Create a secret file owned by root, mode 600:

bash
mkdir -p /srv/podman-dac-demo
echo secret > /srv/podman-dac-demo/secret.txt
chown root:root /srv/podman-dac-demo/secret.txt
chmod 600 /srv/podman-dac-demo/secret.txt
chmod 755 /srv/podman-dac-demo

Run as UID 999 with SELinux relabel so DAC is the remaining blocker:

bash
podman run --rm --user 999:999 -v /srv/podman-dac-demo:/data:Z docker.io/library/alpine:3.20 cat /data/secret.txt
output
cat: can't open '/data/secret.txt': Permission denied

Fix Unix ownership on the host:

bash
chown 999:999 /srv/podman-dac-demo/secret.txt

Retry the same run — the read succeeds and prints secret. DAC was the problem; SELinux was already satisfied by :Z.


Check SELinux before changing permissions

On RHEL-family hosts, confirm enforcing mode:

bash
getenforce
output
Enforcing

Inspect the label:

bash
ls -Zd ~/podman-permission-demo
output
unconfined_u:object_r:admin_home_t:s0 /root/podman-permission-demo

A directory can have valid owner, group, and mode and still be denied because the SELinux type does not permit container access. That is why chmod 777 does not fix every Podman volume error.


Reproduce an SELinux volume denial

Use a world-writable directory that still carries the wrong SELinux type:

bash
mkdir -p ~/podman-avc-demo
chmod 777 ~/podman-avc-demo

Attempt a write without relabel:

bash
podman run --rm -v ~/podman-avc-demo:/data docker.io/library/alpine:3.20 sh -c 'echo y > /data/out'
output
sh: can't create /data/out: Permission denied

Search recent AVC records (install audit if ausearch is missing):

bash
sudo ausearch -m AVC -ts recent

If that returns no matches, read the audit log directly — denials may lag or filter differently:

bash
sudo grep AVC /var/log/audit/audit.log | tail -2

A typical denial for container access to admin_home_t includes:

  • denied operation (write or create)
  • scontext with container_t (source — the container process)
  • tcontext with admin_home_t (target — your host path)
  • tclass (dir or file)

Exact PID and inode values vary per run. The source and target contexts tell you SELinux is the layer to fix.


Fix SELinux with :Z

:Z applies a private container label to the mounted content:

bash
podman run --rm -v ~/podman-permission-demo:/data:Z docker.io/library/alpine:3.20 sh -c 'echo test > /data/write-test.txt'

Verify the relabel:

bash
ls -Zd ~/podman-permission-demo
output
system_u:object_r:container_file_t:s0:c153,c920 /root/podman-permission-demo

The write succeeds and write-test.txt contains test.


Fix shared SELinux content with :z

Lowercase :z relabels for sharing among multiple containers. Prepare data:

bash
mkdir -p ~/podman-z-demo
echo shared > ~/podman-z-demo/data.txt

Without relabel, read fails:

bash
podman run --rm -v ~/podman-z-demo:/data docker.io/library/alpine:3.20 cat /data/data.txt
output
cat: can't open '/data/data.txt': Permission denied

With :z, two containers can read and append. The first container reads the existing file:

bash
podman run --rm -v ~/podman-z-demo:/data:z docker.io/library/alpine:3.20 cat /data/data.txt
output
shared

A second container appends without error:

bash
podman run --rm -v ~/podman-z-demo:/data:z docker.io/library/alpine:3.20 sh -c 'echo c1 >> /data/data.txt'

A third run adds another line the same way:

bash
podman run --rm -v ~/podman-z-demo:/data:z docker.io/library/alpine:3.20 sh -c 'echo c2 >> /data/data.txt'

Confirm both append operations landed on the host:

bash
cat ~/podman-z-demo/data.txt
output
shared
c1
c2

The directory label becomes shared container_file_t:

bash
ls -Zd ~/podman-z-demo
output
system_u:object_r:container_file_t:s0 /root/podman-z-demo

Podman :z vs :Z

Option SELinux behavior Use case
:z shared relabel same content shared among multiple containers
:Z private relabel content intended for a specific container or security context

Lowercase z does not mean read-only, and uppercase Z does not mean read-write. Both control SELinux labeling, not Unix write mode.

Containers in the same Podman pod share one SELinux label, which changes how :Z behaves among pod members. Relabeling walks the source tree recursively — large directories can delay container startup.


Do not use :z or :Z blindly on system directories

Do not recursively relabel broad system paths or an entire home tree just to make one container mount work. Treat these with extreme caution:

  • /home
  • /var
  • /etc
  • /usr

Changing their SELinux labels can cause other confined host services to lose access. Podman upstream documentation warns against relabeling system content. If a workload genuinely needs a broad system directory, design the mount path deliberately instead of applying :z or :Z to the parent.

--security-opt label=disable turns off SELinux separation for that container. Mention it only when that trade-off is intentional — it is not the normal fix for volume permission denied.


When persistent SELinux labels are better than :z / :Z

For a dedicated application path such as /srv/podman-data, create the directory and a sample file:

bash
sudo mkdir -p /srv/podman-data && echo app | sudo tee /srv/podman-data/app.txt >/dev/null

Record the expected SELinux label in policy:

bash
sudo semanage fcontext -a -t container_file_t '/srv/podman-data(/.*)?'

Apply the rule to existing files:

bash
sudo restorecon -Rv /srv/podman-data

Confirm the context:

bash
ls -Zd /srv/podman-data /srv/podman-data/app.txt
output
unconfined_u:object_r:container_file_t:s0 /srv/podman-data
unconfined_u:object_r:container_file_t:s0 /srv/podman-data/app.txt

Mount without ad-hoc relabel flags:

bash
podman run --rm -v /srv/podman-data:/data docker.io/library/alpine:3.20 cat /data/app.txt
output
app

semanage fcontext survives future relabel operations better than one-off chcon. Do not assign container_file_t to arbitrary system directories without considering what else uses them.


When semodule -DB is useful

Some denials are hidden by dontaudit rules. For difficult SELinux cases only:

bash
sudo semodule -DB

Reproduce the failure, inspect AVC messages, then restore normal policy:

bash
sudo semodule -B

Most ordinary :Z failures do not need this step. Treat it as an advanced diagnostic, not part of every workflow.


Fix supplementary group access in rootless containers

If the host user reaches a bind mount only through a supplementary group, that group may not appear inside a rootless container. Start with the host identity:

bash
id

Then compare the default groups inside a throwaway container:

bash
podman run --rm docker.io/library/alpine:3.20 id

Podman supports passing supplementary groups with:

bash
podman run --help

Look for --group-add keep-groups in the help output. For rootless containers, --group-add keep-groups passes the invoking user's supplementary group access into the container. Podman documents this option when the user only has access to a volume through a supplementary group. Check runtime support on your host before relying on it. When group-only access is the pattern, test keep-groups after confirming UID mapping and SELinux are already correct.


NFS and distributed filesystems: diagnose separately

Two distinct problems get lumped together as NFS permission denied:

  • Rootless Podman storage on NFS — NFS, Lustre, and GPFS/Spectrum Scale do not support rootless container storage that depends on user namespaces. If your home directory or default graphroot lives on NFS, move rootless storage to local disk — see Podman storage location.
  • Bind-mounted NFS lacking xattr support — SELinux relabel on an NFS export may fail with lsetxattr ... operation not permitted.

Identify the filesystem under your path:

bash
findmnt -T /srv/podman-data

Sample output on the lab host (local XFS, not NFS):

output
TARGET SOURCE                FSTYPE OPTIONS
/      /dev/mapper/rhel-root xfs    rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota

When findmnt shows NFS or another remote filesystem, check whether the server allows the extended attributes SELinux relabeling needs. Repeated :z attempts will not help if the filesystem cannot store labels.


Fix comparison table

Problem Evidence Typical fix
ordinary Unix ownership UID lacks write access correct owner or mode on host
rootless UID mapping odd host IDs; podman unshare ls -ln mismatch keep-id, podman unshare chown, careful :U
SELinux private mount AVC plus wrong type :Z
SELinux shared mount multiple containers need same data :z
persistent application SELinux path dedicated stable host path semanage fcontext plus restorecon
supplementary group only host user relies on secondary group investigate --group-add keep-groups
rootless Podman storage on NFS graphroot or home on NFS move graphroot to local storage
filesystem cannot relabel xattr or relabel operation fails filesystem-specific resolution
rootful ownership mapping without chown suitable filesystem and kernel idmapped mount (idmap, rootful only)

Troubleshooting

Symptom Likely cause Fix
Permission denied with chmod 777 on host SELinux type blocks container ls -Zd; :Z or :z; or persistent container_file_t
Permission denied with correct ls -Z Unix UID or GID mismatch podman exec … id; chown or :U after confirming container user
Rootless file owned by host user still fails SELinux on home directory :z or :Z with keep-id
please use unshare with rootless ran podman unshare as root run as the unprivileged rootless user
lsetxattr operation not permitted remote FS without label support change filesystem or export options; do not loop :z
keep-id still shows UID 0 started Podman as root run rootless as the target user
idmap mount permission denied rootless idmap is rootful-only use :U, unshare chown, or keep-id instead

References


Summary

Podman volume permission denied is a symptom, not a diagnosis. Unix mode bits, rootless UID mapping, SELinux types, and filesystem capabilities each produce the same error string. The quick table and decision tree at the top of this guide tell you which checks to run before you touch :z, :Z, or :U.

On the lab host, a write to ~/podman-permission-demo failed with SELinux enforcing even though the directory looked like a normal root-owned path — :Z relabeled it to container_file_t and the write succeeded. A separate /srv/podman-dac-demo test failed on Unix ownership until chown 999:999 matched the container user, even with :Z already applied. Rootless mapping showed the same file as UID 1015 on the host and UID 0 inside podman unshare ls -ln, which is the gap keep-id and podman unshare chown are meant to close.

Do not relabel /home or /var to silence one mount, and do not treat chmod 777 or disabling SELinux as troubleshooting steps. When labels must persist on a dedicated path, semanage fcontext plus restorecon beats repeated ad-hoc relabels. For mount-type choice and backup workflows, continue with bind mount vs volume and Podman volumes.


Frequently Asked Questions

1. Why does chmod 777 not fix Podman volume permission denied?

chmod only changes Unix discretionary access control. On SELinux-enforcing hosts, a directory can be world-writable and still deny container access when the SELinux type is wrong. Rootless Podman adds user-namespace UID mapping, so the container process may not be the host UID you expect even when mode bits look generous.

2. What is the difference between Podman :z and :Z?

Both relabel mounted content for container use. Lowercase z applies a shared container content label so multiple containers can access the same relabeled path. Uppercase Z applies a private label for one container security context. Neither option changes Unix read or write mode bits.

3. Does Podman :U change files on the host?

Yes. The U mount option recursively changes owner and group on the host source tree to the host UID and GID that correspond to the container configured user. It is not a virtual mapping inside the container only. Use it only when changing host ownership is acceptable.

4. Can I use idmapped mounts with rootless Podman?

No. Podman idmapped bind mounts are rootful-only today. The kernel does not let an unprivileged rootless user create the idmapped mount Podman needs. Rootless workloads should use keep-id, podman unshare chown, or U after diagnosing ownership, not idmap.

5. Why does podman unshare fail when I run it as root?

podman unshare enters the user namespace Podman uses for rootless containers. On a rootful host it exits with please use unshare with rootless because there is no rootless mapping to enter. Run unshare, unshare chown, and unshare ls as the same unprivileged user that runs rootless Podman.
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)