Overview & Root Cause Summary: The error
Permission denied(orEACCES: permission denied) inside a Docker container mounting a host volume occurs when the containerized application process attempts to read, write, or create files within a bind-mounted directory without sufficient filesystem permissions. The root cause is almost always a mismatch between the User ID (UID) / Group ID (GID) running inside the container versus the host directory ownership, or security enforcement by SELinux on Red Hat/CentOS/Fedora systems blocking container access.
Understanding the Root Causes
- UID / GID Mismatch: Official Docker images frequently run as a non-root user (e.g.,
nodewith UID 1000,postgreswith UID 999,www-datawith UID 33). If the bind-mounted directory on the host is owned byroot(UID 0) or a different host user (e.g., UID 1001) with755permissions, the container user has no write access. - SELinux Enforcing Mode: On SELinux-enabled systems (RHEL, CentOS, Rocky Linux, Fedora), containers are confined by default MCS/MLS labels (
container_t). Host files lacking the proper container volume label (container_file_t) trigger permission denial regardless of standard POSIX permissions. - Rootless Docker / User Namespaces: When user namespace remapping (
userns-remap) or Rootless Docker is active, container UID 0 maps to an unprivileged sub-UID on the host, preventing modifications to standard host directories. - Immutable or Read-Only Mounts: The volume was mounted with the
:roflag or the underlying host partition was mounted read-only.
Step 1: Quick Fix (Align Container User with Host UID/GID at Runtime)
Directly pass your current host user’s UID and GID to the container at launch so all created files match host ownership.
# 1. Inspect your current host user's UID and GID:
id -u
# Example output: 1000
id -g
# Example output: 1000
# 2. Run the container with matching UID and GID via the --user flag:
docker run -d \
--name my-app \
--user $(id -u):$(id -g) \
-v $(pwd)/data:/app/data \
my-image:latest
# 3. For Docker Compose, pass user mapping in docker-compose.yml:
services:
app:
image: my-image:latest
user: "${UID:-1000}:${GID:-1000}"
volumes:
- ./data:/app/data
Step 2: Adjust Host Directory Ownership and Permissions
If the container must run under a fixed internal service UID (such as node at 1000 or www-data at 33), adjust host directory ownership to match the image’s internal user.
# 1. Identify the internal UID used by the container:
docker run --rm my-image:latest id
# Example output: uid=1000(node) gid=1000(node) groups=1000(node)
# 2. Change ownership of the host directory to match the container UID:
sudo chown -R 1000:1000 ./data
# 3. Alternatively, grant group write permissions if sharing across team members:
sudo chmod -R 775 ./data
sudo usermod -aG <target_group> $USER
Step 3: Resolve SELinux Restrictions with the :z or :Z Volume Flags
On systems running SELinux in Enforcing mode, append automatic relabeling flags to the volume declaration.
# 1. Check if SELinux is actively enforcing:
getenforce
# If output is "Enforcing", SELinux requires container volume labels.
# 2. Use the :z flag for volumes shared across multiple containers:
docker run -d \
-v $(pwd)/data:/app/data:z \
my-image:latest
# 3. Use the :Z flag for dedicated, private container volumes (recommended for isolated apps):
docker run -d \
-v $(pwd)/data:/app/data:Z \
my-image:latest
# In docker-compose.yml:
volumes:
- ./data:/app/data:Z
Verification & Testing Steps
Test write permissions inside the container and verify file ownership on the host filesystem.
# 1. Execute a test file creation inside the running container:
docker exec -it my-app touch /app/data/test_write.tmp
# 2. Verify file existence and ownership on the host:
ls -la ./data/test_write.tmp
# 3. Clean up the test file:
rm ./data/test_write.tmp
# 4. Check SELinux security context on the host directory (if applicable):
ls -ldZ ./data
# Should display system_u:object_r:container_file_t:s0
Summary Comparison Table
| Remediation Method | Primary Root Cause | Host Security Impact | Recommended Environment |
|---|---|---|---|
--user $(id -u):$(id -g) |
Host UID/GID mismatch | None (Safest, preserves user perms) | Local development & CI runners |
chown -R <UID>:<GID> |
Fixed container daemon UID | Changes host directory owner | Dedicated server / VM storage |
:z or :Z Mount Flag |
SELinux blocking access | Applies container_file_t context |
RHEL / CentOS / Rocky Linux / Fedora |
chmod 777 ./data |
Generic permission block | High security risk (Not recommended) | Avoid in production |
Leave a Reply