[Fixed] Docker: Permission denied When Writing to Mounted Volume: Step-by-Step Troubleshooting Guide

Overview & Root Cause Summary: The error Permission denied (or EACCES: 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., node with UID 1000, postgres with UID 999, www-data with UID 33). If the bind-mounted directory on the host is owned by root (UID 0) or a different host user (e.g., UID 1001) with 755 permissions, 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 :ro flag 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

Discover more from Victor's room

Subscribe now to keep reading and get access to the full archive.

Continue reading