SE
STACKENGINE
Zero-Trust Client IDE/ Security

Docker SELinux Volume Mount Permission Fix

SELinux enforcing mode blocks Docker volume mounts with EACCES unless the correct MCS label or relabeling option is applied.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(1L)
AI Audit Console
> Waiting for pre-flight audit...
AirLock HYBRID KERNEL: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPECUpdated 2026

How to Fix Docker 'Permission Denied' on Volume Mounts When SELinux Is Enforcing

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–15 mins


TL;DR

  • What broke: SELinux denied the container process access to the host path because the bind-mount lacks a matching MCS/type label (container_file_t). The kernel AVC audit fires and the syscall returns EACCES.
  • How to fix it: Append :z (shared) or :Z (private, unshared) to the volume mount flag, or run chcon -Rt container_file_t /host/path on the host before mounting.
  • Shortcut: Use our Client-Side Sandbox above to auto-refactor your failing docker run command or Compose file without leaking secrets.

The Incident (What Does the Error Mean?)

Raw kernel audit log and Docker stderr you will see:

type=AVC msg=audit(1700000000.000:512): avc: denied { read write } for pid=3821
  comm="app" name="data" dev="sda1" ino=131073
  scontext=system_u:system_r:container_t:s0:c123,c456
  tcontext=unconfined_u:object_r:default_t:s0
  tclass=dir permissive=0

docker: Error response from daemon: error while creating mount source path
  '/host/path': mkdir /host/path: permission denied.

Immediate consequence: The container process (container_t) attempted to access a host directory labeled default_t. SELinux enforcing mode hard-blocks the syscall. The container exits immediately — no graceful degradation, no retry. If this is a stateful workload (database, log aggregator, secrets store), data is not written and the service is dead.


The Attack Vector / Blast Radius

This is a security control working correctly — but engineers under pressure will disable it incorrectly, creating a real attack surface:

The dangerous "fix" engineers reach for first:

# DO NOT DO THIS IN PRODUCTION
setenforce 0

Setting SELinux to permissive globally disables mandatory access control for every process on the host, not just Docker. A container escape (e.g., CVE-2019-5736 runc breakout, or a misconfigured privileged container) now has no kernel-level MAC enforcement standing between the attacker and host root. On a multi-tenant node running multiple workloads, this is a lateral movement highway.

Blast radius of setenforce 0:

  • All containers on the node lose MCS isolation between each other.
  • Host paths previously protected by svirt_sandbox_file_t labeling are now readable by any container process.
  • Compliance frameworks (PCI-DSS 2.2.6, STIG RHEL-07-020210) flag this as a critical finding. Audit trails show the change.

How to Fix It (The Solution)

Basic Fix — Relabeling Flag on the Mount

Append :z or :Z to the -v flag. Docker will call chcon on the host path automatically.

- docker run --rm -v /host/path:/container/path myimage
+ docker run --rm -v /host/path:/container/path:Z myimage

:Z vs :z — get this right:

Flag SELinux Label Applied Use Case
:z container_file_t shared (s0) Multiple containers need access to same path
:Z container_file_t private MCS (s0:cX,cY) Single container, strongest isolation

⚠️ Never use :Z on system directories like /etc, /usr, /var/lib. It will relabel them to container_file_t and break the host. Use only on application-owned data directories.

Manual `chcon` Fix (When You Can't Modify the Run Command)

# Apply the correct type label to the host directory recursively
chcon -Rt container_file_t /host/path

# Verify
ls -lZ /host/path
# Expected: unconfined_u:object_r:container_file_t:s0

Enterprise Best Practice — Persistent Label via `semanage fcontext`

chcon is not persistent across restorecon runs or filesystem relabels. In production, use semanage:

- chcon -Rt container_file_t /host/path
+ semanage fcontext -a -t container_file_t "/host/path(/.*)?" 
+ restorecon -Rv /host/path

This writes the rule to the SELinux policy database. A full filesystem relabel (touch /.autorelabel; reboot) will not revert it.

Docker Compose (v3) Fix

  volumes:
-   - /host/path:/container/path
+   - /host/path:/container/path:Z

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure browser-native utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Checkov — Catch Missing SELinux Labels in Compose Files

Add to your pipeline:

# .checkov.yml
checks:
  - CKV_DOCKER_*
custom_checks:
  - name: REQUIRE_SELINUX_VOLUME_LABEL
    resource: docker-compose.yml
    check: volumes[*] matches ":z" or ":Z"
    severity: HIGH

2. OPA/Gatekeeper — Enforce on Kubernetes (if migrating)

If you are moving these workloads to Kubernetes on an SELinux host, enforce via admission controller:

package selinux_volume

deny[msg] {
  input.request.kind.kind == "Pod"
  vol := input.request.object.spec.volumes[_]
  vol.hostPath
  not input.request.object.spec.securityContext.seLinuxOptions
  msg := sprintf("Pod '%v' uses hostPath volume without seLinuxOptions", [input.request.object.metadata.name])
}

3. Pre-flight Shell Check in Deployment Scripts

#!/usr/bin/env bash
set -euo pipefail

HOST_PATH="/host/path"
EXPECTED_CONTEXT="container_file_t"

ACTUAL=$(stat -c %C "${HOST_PATH}" | awk -F: '{print $3}')
if [[ "${ACTUAL}" != "${EXPECTED_CONTEXT}" ]]; then
  echo "[FATAL] SELinux context on ${HOST_PATH} is '${ACTUAL}', expected '${EXPECTED_CONTEXT}'. Run: chcon -Rt container_file_t ${HOST_PATH}"
  exit 1
fi

Plug this into your pre-deploy hook in Jenkins, GitHub Actions, or ArgoCD sync waves. It fails fast before the container ever starts.

Further Exploration / Matrix Routing