The :z and :Z flags on Podman volume mounts are not about SELinux permissions in the traditional sense, but about labeling the context of the mounted volume so that SELinux allows container processes to access it.
Let’s see this in action. Imagine we have a simple web server container that needs to serve files from a host directory.
First, create a directory on your host and put a file in it:
mkdir -p /home/user/my-web-content
echo "<h1>Hello from SELinux-labeled volume!</h1>" > /home/user/my-web-content/index.html
Now, let’s try to run a container without the :z or :Z flag, and see what happens. We’ll use nginx for this example.
podman run -d --name my-nginx -p 8080:80 \
-v /home/user/my-web-content:/usr/share/nginx/html:ro \
nginx
If SELinux is enforcing, this container will likely fail to start, or more commonly, the web server inside will be unable to read the files. You might see errors like "Permission denied" within the container’s logs, even though ls -l on the host shows the user running Podman has read access.
Let’s check the container logs:
podman logs my-nginx
You’ll probably see errors from nginx indicating it can’t access /usr/share/nginx/html/index.html.
The problem here is that SELinux, by default, confines processes to specific security contexts. When you mount a host directory into a container, SELinux doesn’t automatically know that the container process should have access to that specific host directory’s context. The :z and :Z flags are Podman’s way of telling the system how to adjust the SELinux context of the mounted volume.
The :z Flag (Shared Context)
The :z flag tells Podman to relabel the host directory with a shared content SELinux context. This context, typically container_file_t, is designed to be readable and writable by multiple containers. It’s the most common choice when you want to share data between containers or when the container process needs to write to the volume.
Let’s try it with :z:
# Stop and remove the previous container
podman stop my-nginx
podman rm my-nginx
# Run with :z flag
podman run -d --name my-nginx-z -p 8080:80 \
-v /home/user/my-web-content:/usr/share/nginx/html:ro:z \
nginx
Now, check the logs again:
podman logs my-nginx-z
This time, the nginx server should be able to read index.html and serve the page. You can verify by visiting http://localhost:8080 in your browser.
Why it works: Podman, when it mounts /home/user/my-web-content into the container at /usr/share/nginx/html, uses the chcon -t container_file_t command (or equivalent system call) on the host’s /home/user/my-web-content directory. The container_file_t label is part of the SELinux policy that allows processes running in a container domain (like container_t) to access files labeled container_file_t. The :ro flag still enforces read-only from the container’s perspective, but the SELinux context is set for shared access.
The :Z Flag (Private Context)
The :Z flag (uppercase) is similar to :z, but it relabels the host directory with a private content SELinux context. This context, typically container_file_t, is intended for use by a single container. While it also allows container processes to access the files, it’s generally considered more secure if you don’t intend to share the volume across multiple containers or with the host system in a broadly accessible way. For read-only mounts, the distinction is often minimal in practice, but it’s good to be aware of.
Let’s try it with :Z:
# Stop and remove the previous container
podman stop my-nginx-z
podman rm my-nginx-z
# Run with :Z flag
podman run -d --name my-nginx-Z -p 8080:80 \
-v /home/user/my-web-content:/usr/share/nginx/html:ro:Z \
nginx
Check the logs:
podman logs my-nginx-Z
Again, this should work, serving the index.html file.
Why it works: Similar to :z, Podman applies an SELinux context to the host directory. The difference is subtle and often related to specific SELinux policies. For basic volume mounts, both :z and :Z often result in the container_file_t label, but :Z implies a more restricted sharing model. If you were to then try and access this volume from another container without using :z or :Z on its mount, you’d likely run into SELinux denials if the second container’s domain doesn’t have permission to access files labeled with the context applied by :Z.
When to Use Which?
- Use
:zwhen you need to share the volume between multiple containers, or when the container needs to write to the volume and you want that data to be accessible by other containers or even potentially the host (though host access depends on other SELinux policies). This is the default for most use cases. - Use
:Zwhen the volume is intended for a single container’s exclusive use and you want to enforce a stricter, more private SELinux context. It’s a good practice for isolation.
Important Note on Persistence
When you use :z or :Z, Podman is modifying the SELinux context on the host system. This change is persistent. If you later access that directory from the host or another container without the appropriate SELinux context, you might encounter issues.
You can check the SELinux context of your host directory:
ls -Zd /home/user/my-web-content
You’ll see the context change after using :z or :Z. For example, it might show unconfined_u:object_r:container_file_t:s0.
If you need to reset the context, you can use chcon on the host:
# Reset to a common context for user home directories, adjust as needed
sudo chcon -Rt svirt_sandbox_file_t /home/user/my-web-content
The core takeaway is that SELinux operates on labels, and :z and :Z are Podman’s mechanism to ensure the volume’s host directory has the correct label for container access, thereby bypassing the "Permission denied" errors that would otherwise occur due to SELinux enforcement.
The next thing you’ll likely encounter is trying to use a volume that is not on a filesystem that supports SELinux extended attributes, or dealing with specific Podman storage drivers that interact with SELinux differently.