How to Fix Permission‑Denied Errors When Mounting Host Paths in Rootless Podman Containers

Why “Permission‑Denied” Pops Up When You Bind‑Mount in Rootless Podman

Rootless Podman runs containers as an unprivileged user. That’s great for security, but it also means the container’s view of the host filesystem is filtered through the user‑namespace mapping. When you try to bind‑mount a host path that the container’s UID/GID can’t access, the mount silently fails and the container reports a permission‑denied error. The problem is not the mount itself; it’s the mismatch between the host’s ownership/SELinux context and the container’s user namespace.

Below is a step‑by‑step guide that shows how to diagnose, fix, and avoid these errors while keeping the container as isolated as possible.


1. Re‑examine the Error

Typical output:

$ podman run --rm -v /srv/data:/data alpine ls /data
ls: cannot open directory '/data': Permission denied

The ls inside the container fails because the container’s UID (usually 1000) cannot read /srv/data on the host. The mount succeeded, but the container’s user cannot access the underlying files.


2. Understand the Root Causes

Cause What Happens Typical Symptoms
User‑namespace mapping Host UID 1000 → container UID 0 (or 1000) depending on --userns Container runs as root but still sees host UID 1000, so it can read/write.
File ownership on host Host file owned by root or another UID Container UID 1000 sees “Permission denied”.
SELinux/AppArmor context SELinux labels restrict access Container reports “Permission denied” even if ownership matches.
Mount options (:ro/:rw) Read‑only mounts block writes Write attempts fail with “Permission denied”.
Group membership Container UID not in required group Access denied to group‑protected files.

The most common culprit is the first two: the container’s UID does not match the host file’s UID, and SELinux is blocking the access.


3. Quick Checklist Before You Fix

  1. Check the host file’s ownership
    ls -lZ /srv/data
    
  2. Verify the container’s effective UID
    podman run --rm alpine id
    
  3. Inspect the mount
    podman inspect <container> | jq '.[0].Mounts'
    
  4. Confirm SELinux status
    getenforce
    

If the container’s UID is 1000 and the host file is owned by root, you’re already halfway to the fix.


4. Fixing Ownership Mismatches

4.1. Align Host Ownership with Container UID

The simplest fix is to change the host file’s ownership to match the container’s UID:

sudo chown -R $(id -u):$(id -g) /srv/data

If you’re running a multi‑user system, consider using a shared group:

sudo groupadd -r podman
sudo usermod -aG podman $(whoami)
sudo chgrp -R podman /srv/data
sudo chmod -R 770 /srv/data

Trade‑off: Changing ownership may affect other services that rely on root ownership. Use a dedicated group to keep the change scoped.

4.2. Use --userns=keep-id

By default, rootless Podman maps the host UID 1000 to container UID 0. --userns=keep-id keeps the same UID inside the container, which eliminates the mismatch:

podman run --rm --userns=keep-id -v /srv/data:/data alpine ls /data

Pros: No need to touch host permissions.
Cons: The container now runs as root, which reduces isolation. Use only when you trust the container image.


5. Dealing with SELinux

SELinux can block access even when ownership is correct. The label system_u:object_r:container_file_t:s0 is the default for container mounts. If the host file has a different context, you’ll see “Permission denied”.

5.1. Relabel the Host Path

sudo semanage fcontext -a -t container_file_t "/srv/data(/.*)?"
sudo restorecon -Rv /srv/data

5.2. Use :Z or :z Mount Options

Podman automatically applies the correct SELinux context when you append :Z (private) or :z (shared) to the bind mount:

podman run --rm -v /srv/data:/data:Z alpine ls /data
  • :Z – the container gets its own context.
  • :z – the context is shared with other containers.

If you’re not using SELinux, these options are harmless but can be omitted.

5.3. Disable SELinux Labeling (Last Resort)

If you’re in a non‑SELinux environment or you have a reason to disable labeling, add:

podman run --security-opt label=disable ...

Security note: Disabling SELinux labeling removes a layer of defense. Use only when you understand the implications.


6. Handling Group Permissions

Some applications require group access (e.g., Docker socket). If the container’s UID is not in the required group, you’ll hit a permission error.

sudo usermod -aG docker $(whoami)

After adding the user to the group, log out and back in to refresh the group list. Then retry the mount.


7. Read‑Only vs. Read‑Write Mounts

If you only need to read from the host, use a read‑only mount:

podman run --rm -v /srv/data:/data:ro alpine cat /data/file.txt

This reduces the attack surface because the container cannot modify host files. If you need write access, ensure the host path is writable by the container’s UID and that SELinux allows writes.


8. Using podman mount for Debugging

Sometimes the mount fails silently. podman mount shows the actual mount point on the host:

$ podman run --rm -v /srv/data:/data alpine sleep 60 &
$ podman mount $(pgrep -f 'sleep 60')
/var/lib/containers/storage/overlay-containers/<id>/userdata/merged

The output is the path where the container’s filesystem is merged on the host. You can inspect it directly:

ls -lZ /var/lib/containers/storage/overlay-containers/<id>/userdata/merged/data

This helps you see if the SELinux label is correct or if the host path has the expected permissions. Once you’ve verified the mount point, kill the container:

kill $!


See also