| 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:
getenforceSample output:
EnforcingInspect Unix permissions on the host path (replace HOST_PATH with your directory):
ls -ld HOST_PATHRead the SELinux label on the same path:
ls -Zd HOST_PATHInspect how the container is configured to run:
podman inspect CONTAINER --format 'User={{.Config.User}}'For rootless Podman, compare host IDs with IDs inside Podman's user namespace:
podman unshare ls -ln HOST_PATHOn 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:
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 constraintsThe 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:
mkdir -p ~/podman-permission-demoRun a container that tries to create a file under the mount:
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:
sh: can't create /data/write-test.txt: Permission deniedThe host path carries admin_home_t, which containers are not allowed to write:
ls -Zd ~/podman-permission-demounconfined_u:object_r:admin_home_t:s0 /root/podman-permission-demoKeep ~/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:
podman run --rm --user 999:999 docker.io/library/alpine:3.20 idSample output:
uid=999(yggdrasil) gid=999(ping) groups=999(ping)Compare that to host ownership in numeric form:
ls -ln /srv/podman-dac-demoThe 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:
podman info --format 'Rootless={{.Host.Security.Rootless}}'On the lab host while logged in as root, Podman reports rootful mode:
Rootless=falseCompare with the login user:
iduid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023Do 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:
podman unshare cat /proc/self/uid_mapSample output for user podmantest (UID 1015 on the host):
0 1015 1
1 1048576 65536The 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:
ls -ln ~/perm-demo-rw-r--r--. 1 1015 1016 3 Aug 22 23:05 x.txtInside Podman's user namespace the same file can appear owned by UID 0:
podman unshare ls -ln ~/perm-demo-rw-r--r--. 1 0 0 3 Aug 22 23:05 x.txtThe 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:
podman unshare chown 999:999 ~/perm-demo/x.txtVerify the namespace view:
podman unshare ls -ln ~/perm-demo/x.txt-rw-r--r--. 1 999 999 3 Aug 22 23:06 x.txtOn the host Podman stores the mapped subordinate IDs instead:
ls -ln ~/perm-demo/x.txt-rw-r--r--. 1 1049574 1049574 3 Aug 22 23:06 x.txtYou 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:
ls -ln /srv/podman-u-demo/total 4
-rw-r--r--. 1 0 0 7 Aug 22 23:04 file.txtRun with a non-root container user and both ownership and SELinux relabel on RHEL:
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:
ls -ln /srv/podman-u-demo/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:
podman run --rm --userns=keep-id docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root)As rootless user podmantest, the same flag maps the real login identity:
podman run --rm --userns=keep-id docker.io/library/alpine:3.20 iduid=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:
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):
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 /dataConceptual difference:
:U → recursively chowns host files
idmap → changes how IDs are presented through the mountPodman 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:
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-demoRun as UID 999 with SELinux relabel so DAC is the remaining blocker:
podman run --rm --user 999:999 -v /srv/podman-dac-demo:/data:Z docker.io/library/alpine:3.20 cat /data/secret.txtcat: can't open '/data/secret.txt': Permission deniedFix Unix ownership on the host:
chown 999:999 /srv/podman-dac-demo/secret.txtRetry 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:
getenforceEnforcingInspect the label:
ls -Zd ~/podman-permission-demounconfined_u:object_r:admin_home_t:s0 /root/podman-permission-demoA 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:
mkdir -p ~/podman-avc-demo
chmod 777 ~/podman-avc-demoAttempt a write without relabel:
podman run --rm -v ~/podman-avc-demo:/data docker.io/library/alpine:3.20 sh -c 'echo y > /data/out'sh: can't create /data/out: Permission deniedSearch recent AVC records (install audit if ausearch is missing):
sudo ausearch -m AVC -ts recentIf that returns no matches, read the audit log directly — denials may lag or filter differently:
sudo grep AVC /var/log/audit/audit.log | tail -2A typical denial for container access to admin_home_t includes:
- denied operation (
writeorcreate) scontextwithcontainer_t(source — the container process)tcontextwithadmin_home_t(target — your host path)tclass(dirorfile)
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:
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:
ls -Zd ~/podman-permission-demosystem_u:object_r:container_file_t:s0:c153,c920 /root/podman-permission-demoThe write succeeds and write-test.txt contains test.
Fix shared SELinux content with :z
Lowercase :z relabels for sharing among multiple containers. Prepare data:
mkdir -p ~/podman-z-demo
echo shared > ~/podman-z-demo/data.txtWithout relabel, read fails:
podman run --rm -v ~/podman-z-demo:/data docker.io/library/alpine:3.20 cat /data/data.txtcat: can't open '/data/data.txt': Permission deniedWith :z, two containers can read and append. The first container reads the existing file:
podman run --rm -v ~/podman-z-demo:/data:z docker.io/library/alpine:3.20 cat /data/data.txtsharedA second container appends without error:
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:
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:
cat ~/podman-z-demo/data.txtshared
c1
c2The directory label becomes shared container_file_t:
ls -Zd ~/podman-z-demosystem_u:object_r:container_file_t:s0 /root/podman-z-demoPodman :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:
sudo mkdir -p /srv/podman-data && echo app | sudo tee /srv/podman-data/app.txt >/dev/nullRecord the expected SELinux label in policy:
sudo semanage fcontext -a -t container_file_t '/srv/podman-data(/.*)?'Apply the rule to existing files:
sudo restorecon -Rv /srv/podman-dataConfirm the context:
ls -Zd /srv/podman-data /srv/podman-data/app.txtunconfined_u:object_r:container_file_t:s0 /srv/podman-data
unconfined_u:object_r:container_file_t:s0 /srv/podman-data/app.txtMount without ad-hoc relabel flags:
podman run --rm -v /srv/podman-data:/data docker.io/library/alpine:3.20 cat /data/app.txtappsemanage 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:
sudo semodule -DBReproduce the failure, inspect AVC messages, then restore normal policy:
sudo semodule -BMost 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:
idThen compare the default groups inside a throwaway container:
podman run --rm docker.io/library/alpine:3.20 idPodman supports passing supplementary groups with:
podman run --helpLook 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
graphrootlives 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:
findmnt -T /srv/podman-dataSample output on the lab host (local XFS, not NFS):
TARGET SOURCE FSTYPE OPTIONS
/ /dev/mapper/rhel-root xfs rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquotaWhen 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
- Podman run manual — volume and user namespace options
- Red Hat Enterprise Linux 10 — Building, running, and managing containers — container volume integration and SELinux shared/private labeling
- SELinux project — container SELinux documentation
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.

