What this error means
The error “OCI runtime create failed: permission denied” occurs when the container runtime (runc/containerd via Docker) cannot start the container due to a host-level access control denial. Common culprits:
- SELinux/AppArmor policy blocks mounts or process actions
- Bind-mounted directory lacks proper permissions or labels
- Missing Linux capabilities or restrictive seccomp profile
- Devices/cgroups/network not permitted (often in rootless Docker)
- Incorrect ownership/permissions under /var/lib/docker or data-root
This guide targets Linux hosts using Docker (DevOps/Docker context).
Quickstart: most common fixes (Linux)
- Verify Docker works at all
- Test a basic container:
docker run --rm alpine:3.20 echo OK
If this fails with the same error, jump to Diagnostics.
- Fix bind-mount + SELinux (Fedora/RHEL/CentOS)
- Add SELinux relabel option to mounts:
docker run --rm -v "$HOME/test:/data":Z alpine:3.20 sh -c 'touch /data/ok'
- Or relabel once on host:
mkdir -p "$HOME/test"
sudo chcon -Rt svirt_sandbox_file_t "$HOME/test"
- AppArmor (Ubuntu/Debian)
- Test with AppArmor unconfined (for debugging only):
docker run --rm --security-opt apparmor=unconfined alpine:3.20 true
If it works, adjust or choose an appropriate profile instead of leaving unconfined.
- Seccomp restrictions
- Test with seccomp unconfined (debugging only):
docker run --rm --security-opt seccomp=unconfined alpine:3.20 true
If it works, switch to a profile that allows the needed syscalls.
- Capabilities
- If the container needs privileged operations (e.g., mount), add capability or run privileged for testing:
docker run --rm --cap-add SYS_ADMIN alpine:3.20 mount | head -n1
# or, last resort for debugging only:
docker run --rm --privileged alpine:3.20 true
- Rootless Docker specifics
- Ensure newuidmap/newgidmap are installed and subuid/subgid configured:
getent passwd "$USER" && grep "^$USER:" /etc/subuid /etc/subgid
- Try with fuse-overlayfs driver or use a named volume instead of privileged bind mounts.
- File/dir permissions
- Ensure host path exists and is accessible:
HOSTDIR="$HOME/test"; mkdir -p "$HOSTDIR"; chmod 755 "$HOSTDIR"
docker run --rm -v "$HOSTDIR:/data" alpine:3.20 sh -c 'touch /data/ok'
- Restart services and update
sudo systemctl restart docker
sudo docker system prune -f
sudo apt/yum update && sudo systemctl restart docker
Minimal working example (SELinux bind-mount fix)
Reproduce the error (on SELinux enforcing hosts):
mkdir -p "$HOME/seltest"
docker run --rm -v "$HOME/seltest:/data" alpine:3.20 sh -c 'touch /data/hello'
# Likely fails with: OCI runtime create failed: ... permission denied
Fix with automatic relabeling:
docker run --rm -v "$HOME/seltest:/data":Z alpine:3.20 sh -c 'touch /data/hello'
# Succeeds
Alternative persistent fix:
sudo chcon -Rt svirt_sandbox_file_t "$HOME/seltest"
docker run --rm -v "$HOME/seltest:/data" alpine:3.20 sh -c 'touch /data/hello'
Root causes and targeted fixes
Bind mounts without proper permissions/labels
- Symptom: Fails when mounting or writing to host path.
- Fix: Ensure path exists, correct owner/perm (e.g., 755/775). On SELinux, use :Z or chcon. Prefer named volumes if you don’t need host path.
SELinux denials (Fedora/RHEL-based)
- Symptom: Error at container start; audit logs show denied mount/access.
- Fix: Use :Z or :z on volumes; avoid disabling SELinux. For persistent paths, chcon -Rt svirt_sandbox_file_t.
AppArmor denials (Ubuntu/Debian)
- Symptom: Default profile blocks some operations.
- Fix: Select an appropriate profile or add rules. For debugging only, apparmor=unconfined.
Seccomp profile too restrictive
- Symptom: Start-up syscalls denied.
- Fix: Use a compatible seccomp profile or temporarily seccomp=unconfined to confirm the cause.
Missing capabilities
- Symptom: Operations like mounting, raw network, or device access fail.
- Fix: Add required capabilities (e.g., --cap-add NET_ADMIN or SYS_ADMIN). Use --privileged only to confirm and then tighten.
Devices and cgroups
- Symptom: Access to /dev/* or cgroup creation denied.
- Fix: Allow device explicitly: --device /dev/fuse; ensure cgroup v2 is supported by your Docker/runc stack; update if needed.
Rootless Docker specifics
- Symptom: Cannot create cgroups, set up networking, or mount overlay.
- Fix: Install newuidmap/newgidmap; configure /etc/subuid and /etc/subgid; use fuse-overlayfs; avoid privileged bind mounts.
Docker data-root permissions
- Symptom: Denial when touching /var/lib/docker (or custom data-root).
- Fix: Ensure root:root ownership and 711/755 perms on each path element.
Quick diagnostics
- Get the exact failure details:
journalctl -u docker -n 200 --no-pager
sudo dmesg | tail -n 50
- Show Docker info (cgroup driver, runtime):
docker info
- Validate with a baseline container:
docker run --rm busybox:1.36 true
If baseline works but your app container fails, compare differences: volumes, capabilities, devices, security opts.
Mapping symptom to likely fix
| Symptom context | Likely cause | Try this first |
|---|---|---|
| Fedora/RHEL, bind mount | SELinux | Add :Z on the -v mount or chcon the path |
| Ubuntu, custom mounts | AppArmor | apparmor=unconfined (debug), then set a profile |
| Needs FUSE or mount | Capabilities/Devices | --cap-add SYS_ADMIN or --device /dev/fuse |
| Rootless mode | Subuid/subgid/cgroups | Install newuidmap/newgidmap; use fuse-overlayfs |
Pitfalls to avoid
- Disabling SELinux or AppArmor permanently; prefer targeted policy or labeling.
- Leaving containers privileged/unconfined after debugging.
- chmod -R 777 on host paths; use correct ownership and minimal perms.
- Ignoring cgroup v2 compatibility; keep Docker, containerd, and runc up to date.
- Mounting non-existent host paths; Docker will create files in unexpected places.
Performance notes
- Prefer named volumes over bind mounts when possible; they avoid host labeling issues and are often faster on overlay2.
- On rootless, fuse-overlayfs can be slightly slower; consider rootful Docker when safe and appropriate.
- Excessive relabeling (:Z on large trees) can slow startup; relabel once with chcon when stable.
- Avoid running with seccomp=unconfined or privileged in production; it may mask bugs and reduce kernel mitigations.
Tiny FAQ
Why does :Z fix this on Fedora/RHEL?
- It sets an SELinux label on the mount so the container’s confined domain may access it.
Is using --privileged safe?
- It’s for debugging. Replace it with the minimal set of capabilities and security opts your app needs.
Does this happen on macOS/Windows?
- Less commonly. The error typically arises on Linux where SELinux/AppArmor/cgroups directly govern the runtime.
My container only fails with a specific host directory. Why?
- That path’s permissions or labels likely differ. Compare owner/perm/xattrs with a working path.
How do I make the fix permanent?
- Script chcon for stable paths, define volumes in compose with :Z, and encode needed caps/security opts in your compose/CI configs.