skip to content

On a RHEL or Fedora host with SELinux in enforcing mode, a container gets "Permission denied" reading a bind-mounted directory even though the files are mode 0777 and owned by the container's user. Explain the cause, and what the `:z` and `:Z` bind-mount options do.

level: seniorimportance: should knowfreq 35%

answer

  1. SELinux checks on top of mode bits — 0777 irrelevant
  2. container_t may only touch container_file_t
  3. MCS category pair isolates container from container
  4. :z shared relabel, :Z private relabel — recursive, permanent
  5. ausearch -m avc; semanage fcontext + restorecon for durability

basics

~20 s

SELinux denies it on top of the file mode: the container runs as a confined type that may only touch files labelled container_file_t. The :z suffix relabels the mount as shared between containers; :Z relabels it private to one container.

solid answer

~50 s

SELinux is a mandatory access control layer checked **in addition to** UNIX permissions, so 0777 is irrelevant if the label is wrong. Container processes run in a confined domain (`container_t`) that is only allowed to read and write files typed `container_file_t`; a random host directory is typed something like `user_home_t` or `default_t`, and access is denied. The denial appears in the audit log as an AVC record, not in the file mode. Appending `:z` to the `-v` option tells the container engine to relabel the host path to `container_file_t` with a **shared** MCS category, so every container may use it. `:Z` relabels it to `container_file_t` with a **private** category pair unique to that one container, so no other container can read it. The caution: relabelling is recursive and permanent on the host. `-v /home:/host:Z` will rewrite labels across your home directory and can break login or other services. Confirm the denial with `ausearch -m avc -ts recent` before reaching for a fix.

code

bash · 9 lines
bash
ls -Z /srv/appdata
ausearch -m avc -ts recent

# per-container relabel (private to this container)
docker run -v /srv/appdata:/data:Z myimage

# durable host-side labelling instead
sudo semanage fcontext -a -t container_file_t "/srv/appdata(/.*)?"
sudo restorecon -Rv /srv/appdata

go deeper

for a junior

Recognise the symptom — permission denied despite permissive file modes on a RHEL-family host — and know that :z or :Z is the usual fix, plus that SELinux is a separate check.

for a middle

Explain the type/label model, distinguish :z (shared) from :Z (private), and know to check the audit log rather than guessing.

for a senior

Cover MCS categories as container-to-container isolation, the recursive and destructive nature of relabelling, and semanage fcontext + restorecon as the durable production answer.

for a principal

Position SELinux as defence in depth behind namespaces, and set policy on where relabelled data directories may live so teams never point :Z at shared host trees or disable the label as a habit.

## Two independent permission systems On a Linux system with SELinux enabled, every file access is checked twice. First the classic discretionary check: does this UID/GID satisfy the inode's mode bits or ACL? Then the mandatory check: does policy allow a process in *this security context* to perform *this operation* on a file with *that security context*? Both must pass. This is why `chmod 777` changes nothing when the failure is an SELinux denial — you fixed the check that was already passing. A security context looks like `system_u:object_r:container_file_t:s0:c123,c456`. The important parts for containers are the **type** (`container_file_t`, `user_home_t`, `default_t`, …) and the **MCS categories** (`c123,c456`). ## How container engines use it When SELinux support is enabled, Docker and Podman start each container's processes in the `container_t` domain. Policy grants `container_t` broad access to files typed `container_file_t` and essentially nothing else on the host filesystem. Image layers unpacked by the engine already carry that type, which is why everything inside the image works normally. On top of the type, the engine assigns each container a unique **MCS category pair** — a random `c<N>,c<M>`. Multi-Category Security says a process may only access an object whose categories are a subset of its own. Two containers get different pairs, so even though both run as `container_t` and both can in principle touch `container_file_t`, they cannot reach into each other's storage. This is the second line of defence behind namespaces. ## Why a bind mount breaks A bind-mounted host path keeps whatever label the host filesystem gave it: `user_home_t` for something under `/home`, `default_t` for an arbitrary directory under `/opt` or `/srv`, `httpd_sys_content_t` for web roots, and so on. `container_t` has no rule permitting access to those types, so the open fails with `EACCES` regardless of mode bits and regardless of who owns the file. ## What :z and :Z do Both are container-engine mount suffixes, not kernel features. They instruct the engine to relabel the host path before starting the container: - **`:z` (lowercase)** — relabel to `container_file_t` with the *shared* category set (effectively `s0` with no private categories). Any container may then access it. Use this for a directory legitimately shared by several containers, such as a config directory read by an app and a sidecar. - **`:Z` (uppercase)** — relabel to `container_file_t` with the *private* category pair belonging to this container only. Strongest isolation; correct for a database data directory owned by exactly one container. A mount can also carry the ordinary options together, e.g. `-v /srv/data:/data:ro,Z`. ## The dangerous part Relabelling is recursive and it mutates the host. `-v /home/alice:/data:Z` rewrites the SELinux label on the entire home directory to a private container category. Other services — sshd reading `.ssh`, the desktop session, a backup agent — are then denied, and the damage persists after the container exits. Never relabel `/`, `/home`, `/usr`, `/var`, or any directory shared with host services. Mount a dedicated subdirectory instead. Relabelling also does not survive a full filesystem relabel or `restorecon`, because the default file-context database still says `user_home_t`. For a persistent host directory, the durable approach is to register the context and apply it: ``` semanage fcontext -a -t container_file_t "/srv/appdata(/.*)?" restorecon -Rv /srv/appdata ``` Then mount it with no `:z`/`:Z` at all. ## Diagnosing before fixing The tell is that the failure is invisible in `ls -l`. Confirm with the audit trail: ``` ausearch -m avc -ts recent sudo dmesg | grep -i denied ls -Z /srv/appdata setenforce 0 # temporary test only — if it now works, it was SELinux ``` An AVC record names the source context (`scontext=...:container_t:s0:c1,c2`), the target context (`tcontext=...:default_t:s0`), and the permission denied (`read`, `write`, `open`). That triple tells you exactly which label to change. ## Escape hatches and when they are wrong `--security-opt label:disable` runs the container unconfined by SELinux, and `--privileged` implies it. Both remove a real isolation boundary — the one that stops a container escape from reading other containers' data — and should be a debugging step, not a deployment choice. Setting SELinux permissive cluster-wide is the same trade at a larger scale. Prefer a correct label on a dedicated directory.

  • When would :z be the right choice over :Z?
    When more than one container legitimately needs the same host path — a shared configuration directory, a shared cache, or an app plus a log-shipping sidecar. `:Z` stamps a category pair unique to a single container, so a second container would be denied. The cost of `:z` is weaker isolation: any container on the host can now read that path.
  • Why can a directory that worked yesterday start failing after a routine host maintenance run?
    `:z`/`:Z` only change the label in place; they do not update the file-context database. A `restorecon -R` sweep, a package update that triggers one, or a full filesystem relabel resets the directory to its default type such as `default_t`, and the container is denied again. Registering the context with `semanage fcontext` makes it durable.

Mode bits are the lock on the door; SELinux is a badge reader on the corridor. A wide-open door is useless if your badge does not open the corridor, and :Z is stamping the room with a badge code only one occupant carries.

saying these in an interview costs you the question

  • Reaching for chmod 777 when the denial is an SELinux AVC
  • Applying :Z to /home, /usr, or another shared host tree and breaking host services
  • Believing :z/:Z are kernel mount flags rather than container-engine relabel instructions
  • Recommending setenforce 0 or --security-opt label:disable as the permanent fix
  • Assuming the labels applied by :Z survive restorecon or a filesystem relabel

context