Podman Mounted Directory Permission Denied: A SELinux Gotcha

When mounting host directories to Podman containers, you may encounter Permission denied even when running as root inside the container. This article explains the root cause—SELinux—and provides the fix.

The Problem Scenario

I was configuring a Quadlet-managed service with a .container file like this:

[Container]
Entrypoint=/app/entry.sh
Image=docker.io/library/alpine:3.15
Pod=sniproxy.pod
Volume=/home/nite/pod/sniproxy/data/bin:/app:ro
Volume=/home/nite/pod/sniproxy/data/etc:/config:ro

[Service]
Restart=always

[Install]
WantedBy=default.target

After configuration, running systemctl --user daemon-reload and systemctl --user start sniproxy.service resulted in a failed start.

Checking the logs via journalctl revealed a Permission denied error. To verify, I commented out the Entrypoint, set Exec=sleep infinity, restarted the service, then used podman exec to enter the container. Running ls -l /app also returned Permission denied.

I was root inside the container, and traditional file permissions (owner, group, other) should have granted access. Something else was blocking it.

Root Cause: SELinux

On distributions with SELinux enabled (Fedora, CentOS, RHEL), file access is additionally constrained by SELinux security contexts.

When Podman starts a container, processes inside are assigned an SELinux label, typically container_t. Directories created under the host’s home directory (e.g., ~/sniproxy/data) have a default label of user_home_t.

SELinux’s default policy explicitly forbids processes with the container_t label from accessing user_home_t-labeled directories. This is a deliberate security measure: it prevents container processes (even if compromised) from reading or tampering with personal files on the host.

The Permission denied is not due to traditional permissions, but because SELinux policy blocks container_t from accessing user_home_t.

Solution: Modify the SELinux Label

The fix is to have Podman modify the directory’s SELinux label when mounting. Append one of these flags to the volume definition:

  • :z: Changes the directory label to container_file_t, a shared label that multiple containers can access.
  • :Z: Creates a private label for the directory, accessible only by that container.

For most cases, :z is the right choice.

Before:

Volume=/home/nite/pod/sniproxy/data/bin:/app:ro
Volume=/home/nite/pod/sniproxy/data/etc:/config:ro

After:

Volume=/home/nite/pod/sniproxy/data/bin:/app:ro,z
Volume=/home/nite/pod/sniproxy/data/etc:/config:ro,z

After editing the Quadlet file, run:

systemctl --user daemon-reload
systemctl --user restart sniproxy.service

Now podman exec into the container, and ls -l /app works as expected.