Introduction
Docker volume mount permission denied errors occur when containers cannot read from or write to mounted volumes due to user ID (UID) and group ID (GID) mismatches, SELinux security contexts, or file system permission restrictions. Docker volumes are essential for persistent data storage, sharing data between containers, and mounting application code during development. Permission issues manifest when the container process runs as a different user than the volume owner, when SELinux blocks access to host paths, when Docker Desktop file sharing is misconfigured, or when rootless Docker lacks permissions to access host directories. Common causes include container running as non-root user (UID 1000) mounting host directory owned by root, SELinux preventing container access to host paths (default security policy), named volume created with wrong ownership, Docker Desktop not having file sharing permissions for the mount path, rootless Docker installation without proper user namespace mapping, Windows WSL2 backend path translation issues, and NFS-mounted volumes with restrictive export options. The fix requires understanding Docker's volume driver architecture, Linux permission model, SELinux labeling, user namespace remapping, and platform-specific configuration for Docker Desktop. This guide provides production-proven troubleshooting for Docker volumes across Linux servers, Docker Desktop (Windows/macOS), rootless Docker, and Kubernetes persistent volumes.
Symptoms
Error: Permission deniedwhen writing to mounted volume- Container logs show
EACCES: permission denied, open '/data/file' - Application fails to start with database volume errors
cannot create directory: Permission deniedin container- Named volume appears empty despite data written
- Host directory shows files owned by root after container write
- Docker Desktop shows "Sharing violated" or path access errors
- SELinux audit logs show
avc: denied { write }for container - Rootless Docker cannot access
/var/lib/docker/volumes - WSL2 mount path not accessible from container
- Files created in container have wrong UID/GID on host
- Read-only filesystem errors on supposedly read-write mount
Common Causes
- UID/GID mismatch between container user and volume owner
- SELinux blocking container access to host paths
- Docker Desktop file sharing not enabled for mount path
- Named volume created with root ownership
- Rootless Docker user namespace remapping
- Windows path translation issues in WSL2
- NFS export options blocking container access
- Bind mount source directory permissions too restrictive
- Docker volume driver configuration issues
- macOS file system sharing limitations
Step-by-Step Fix
### 1. Diagnose volume permission issues
Check volume mount configuration:
```bash # Inspect container mount configuration docker inspect <container_id> --format '{{json .Mounts}}' | jq
# Output shows: # [ # { # "Type": "volume", # "Name": "myapp_data", # "Source": "/var/lib/docker/volumes/myapp_data/_data", # "Destination": "/data", # "RW": true, # "Propagation": "" # } # ]
# Check bind mount permissions on host ls -la /path/to/host/directory
# Check named volume location docker volume inspect myapp_data --format '{{.Mountpoint}}'
# Check volume directory ownership ls -la /var/lib/docker/volumes/myapp_data/_data ```
Check container user context:
```bash # Check which user container runs as docker inspect <container_id> --format '{{.Config.User}}'
# Empty = running as root (default) # "1000" = running as UID 1000 # "node" = running as named user
# Check running processes inside container docker exec <container_id> id docker exec <container_id> whoami
# Check file ownership from container perspective docker exec <container_id> ls -la /data ```
Check SELinux status:
```bash # Check if SELinux is enforcing getenforce
# Check SELinux context of mount point ls -Z /path/to/host/directory
# Check for SELinux denials sudo ausearch -m avc -ts recent | grep docker sudo grep denied /var/log/audit/audit.log | grep docker
# Expected contexts: # container_t - Container processes # container_file_t - Container accessible files # container_var_lib_t - Docker data ```
### 2. Fix bind mount permissions
Change host directory ownership:
```bash # WRONG: Directory owned by root, container runs as UID 1000 ls -la /srv/myapp/data # drwxr-xr-x 2 root root 4096 Jan 15 10:00 data
# Container running as UID 1000 cannot write
# CORRECT: Change ownership to match container user sudo chown 1000:1000 /srv/myapp/data # Or with username if known sudo chown appuser:appgroup /srv/myapp/data
# For Docker running as root, container as non-root: # Match container UID/GID on host directory ```
Use USER directive in Dockerfile:
```dockerfile # WRONG: Running as root, files created as root FROM node:18-alpine WORKDIR /app COPY . . CMD ["node", "server.js"]
# Container creates files as root (UID 0) # Host directory shows root-owned files
# CORRECT: Create user and switch before running FROM node:18-alpine
# Create user with specific UID/GID RUN addgroup -g 1000 appgroup && \ adduser -u 1000 -G appgroup -s /bin/sh -D appuser
WORKDIR /app COPY --chown=appuser:appgroup . .
# Switch to non-root user USER appuser
CMD ["node", "server.js"]
# Files created in /app owned by UID 1000 # Match host directory to UID 1000 ```
Set proper permissions:
```bash # Minimum permissions for container access chmod 755 /srv/myapp/data # Owner: read/write/execute # Group: read/execute # Other: read/execute
# For shared write access chmod 775 /srv/myapp/data # Owner and group can write
# For private data (container only) chmod 700 /srv/myapp/data # Only owner can access
# Set ACL for specific container UID sudo setfacl -m u:1000:rwX /srv/myapp/data ```
### 3. Fix SELinux volume permissions
Use SELinux volume labels:
```bash # Add :z suffix for single-container volume (private label) docker run -d \ -v /srv/myapp/data:app/data:z \ myapp
# :z = Private unshared label (one container) # SELinux relabels directory for exclusive container use
# Add :Z suffix for shared multi-container volume docker run -d \ -v /srv/myapp/data:app/data:Z \ myapp
docker run -d \ -v /srv/myapp/data:app/data:Z \ another-app
# :Z = Shared label (multiple containers can access) # SELinux allows multiple containers to share
# Check label applied ls -Z /srv/myapp/data # Should show container_file_t context ```
Manually set SELinux context:
```bash # Set container-readable context sudo chcon -Rt container_file_t /srv/myapp/data
# Verify context ls -Z /srv/myapp/data # drwxr-xr-x. user group unconfined_u:object_r:container_file_t:s0 data
# Restore default context (removes container access) sudo restorecon -Rv /srv/myapp/data
# Make context permanent (survives relabel) sudo semanage fcontext -a -t container_file_t "/srv/myapp/data(/.*)?" sudo restorecon -Rv /srv/myapp/data ```
Disable SELinux for Docker (development only):
```bash # Check SELinux status sestatus
# Temporarily set to permissive (logs but doesn't block) sudo setenforce 0
# Test if SELinux was causing issue
# Re-enable enforcing sudo setenforce 1
# Or add SELinux exemption for Docker sudo semanage permissive -a container_t
# View permissive domains sudo semanage permissive -l
# Remove permissive sudo semanage permissive -d container_t ```
### 4. Fix Docker Desktop file sharing (Windows/macOS)
Enable file sharing on macOS:
```bash # Docker Desktop > Settings > Resources > File Sharing
# Add directories that can be mounted: # - /Users (default) # - /Volumes (for external drives) # - Custom paths
# Or edit settings.json directly # ~/Library/Group Containers/group.com.docker/settings.json { "fileSharingDirectories": [ "/Users", "/Volumes", "/private", "/tmp", "/var/folders" ] }
# Restart Docker Desktop after changes
# For custom project directories: # 1. Open Docker Desktop # 2. Go to Settings > Resources > File Sharing # 3. Click "+" to add your project directory # 4. Click "Apply & Restart" ```
Enable file sharing on Windows:
```powershell # Docker Desktop > Settings > Resources > File Sharing
# Ensure drive is shared: # - Local drives (C:, D:, etc.) # - Network drives (may require additional config)
# Check WSL2 backend settings # Docker Desktop > Settings > General > "Use WSL 2 based engine"
# For WSL2 mounts: # Path format: /mnt/c/path/to/data # Or: \\wsl$\Ubuntu\home\user\project
# Enable file sharing in WSL2 # Edit /etc/wsl.conf in WSL distribution: # [automount] # enabled = true # options = "metadata,uid=1000,gid=1000"
# Restart WSL # wsl --shutdown ```
Fix macOS permission issues:
```bash # On macOS host, check directory permissions ls -la /Users/username/project/data
# Docker Desktop runs as your user, so match ownership chmod 755 /Users/username/project/data
# For external drives, ensure they're formatted with macOS-compatible FS # APFS or HFS+ recommended # exFAT works but doesn't support Unix permissions
# Check Docker Desktop can access path docker run --rm -v /Users/username/project/data:/data alpine ls -la /data
# If fails with "permission denied": # 1. Check File Sharing settings # 2. Verify path exists and is readable # 3. Try full path without symlinks ```
Fix Windows path issues:
```powershell # Use forward slashes or escaped backslashes docker run -v "C:/Users/username/data:/data" myapp docker run -v "C:\\Users\\username\\data:/data" myapp
# Or use WSL2 path format docker run -v /mnt/c/Users/username/data:/data myapp
# Check Docker Desktop file sharing # Settings > Resources > File Sharing > Ensure C: drive is listed
# For network drives, use UNC path docker run -v "\\server\share\data:/data" myapp
# May need to authenticate first net use Z: \\server\share\data docker run -v "Z:/data" myapp ```
### 5. Fix named volume permissions
Change named volume ownership:
```bash # Named volumes are owned by root by default docker volume inspect myapp_data # Shows Mountpoint: /var/lib/docker/volumes/myapp_data/_data
# Check ownership ls -la /var/lib/docker/volumes/myapp_data/_data # drwxr-xr-x 2 root root 4096 Jan 15 10:00 _data
# Option 1: Run container as root to initialize docker run --rm -v myapp_data:/data alpine chown -R 1000:1000 /data
# Option 2: Use busybox to change ownership docker run --rm -v myapp_data:/data busybox chown -R appuser:appgroup /data
# Option 3: Initialize with Dockerfile volume docker run --rm myapp chown -R 1000:1000 /data ```
Use init container pattern:
```yaml # docker-compose.yml version: '3.8'
services: init-permissions: image: alpine volumes: - myapp_data:/data command: chown -R 1000:1000 /data user: root # Run as root to change ownership
app: build: . volumes: - myapp_data:/data user: "1000:1000" # Run as non-root depends_on: init-permissions: condition: service_completed_successfully
volumes: myapp_data: ```
Create volume with specific options:
```bash # Create local volume with specific permissions docker volume create \ --driver local \ --opt type=none \ --opt device=/srv/myapp/data \ --opt o=bind \ myapp_data
# For NFS volumes with permission options docker volume create \ --driver local \ --opt type=nfs \ --opt device=:/export/data \ --opt o=addr=nfs-server,rw,nolock \ myapp_nfs_data
# Check volume options docker volume inspect myapp_data ```
### 6. Fix rootless Docker volume permissions
Configure rootless Docker:
```bash # Check if running rootless echo $DOCKER_HOST # unix:///run/user/1000/docker.sock = rootless # unix:///var/run/docker.sock = rootful
# Rootless Docker stores volumes in user directory ls -la ~/.local/share/docker/volumes/
# Rootless Docker has limited host path access # Can only access paths user can read/write
# Bind mount from home directory (works) docker run -v ~/data:/data myapp
# Bind mount from system directory (fails) docker run -v /srv/data:/data myapp # Permission denied - rootless Docker can't access /srv
# Fix: Move data to user directory mkdir -p ~/docker-volumes/myapp_data docker run -v ~/docker-volumes/myapp_data:/data myapp ```
Configure user namespace remapping:
```bash # Check user namespace configuration cat /etc/subuid cat /etc/subgid
# Expected output: # username:100000:65536 # Maps container UIDs 0-65535 to host UIDs 100000-165535
# For rootless Docker, user namespace is automatic # Container UID 0 maps to your user UID (e.g., 1000)
# Files created by container "root" appear as your user on host docker run --rm -v ~/data:/data alpine touch /data/test ls -la ~/data/ # -rw-r--r-- 1 username username 0 Jan 15 10:00 test
# This is expected behavior for rootless Docker ```
Fix rootless Docker volume access:
```bash # If rootless Docker can't access volume:
# 1. Check user owns the path ls -la ~/.local/share/docker/volumes/
# 2. Ensure proper permissions chmod 755 ~/.local/share/docker/volumes/
# 3. Check SELinux (if enabled) ls -Z ~/.local/share/docker/volumes/
# 4. Restart rootless Docker systemctl --user restart docker
# 5. Check Docker is running docker ps ```
### 7. Fix NFS volume permissions
Configure NFS export options:
```bash # On NFS server, edit /etc/exports /srv/docker-data *(rw,sync,no_subtree_check,no_root_squash)
# Key options: # - rw: Read-write access # - sync: Synchronous writes (safer) # - no_subtree_check: Disable subtree checking # - no_root_squash: Allow root in containers (security risk!)
# Safer alternative with all_squash: /srv/docker-data *(rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000)
# all_squash: Map all UIDs to anonuid # anonuid=1000: Map to specific UID # anongid=1000: Map to specific GID
# Apply changes exportfs -ra
# Verify exports exportfs -v ```
Mount NFS volume in Docker:
```bash # Create Docker volume with NFS driver docker volume create \ --driver local \ --opt type=nfs \ --opt o=addr=192.168.1.100,rw,nolock \ --opt device=:/srv/docker-data \ nfs_data
# Run container with NFS volume docker run -d \ -v nfs_data:/data \ myapp
# Check NFS mount inside container docker exec myapp mount | grep nfs ```
Troubleshoot NFS permissions:
```bash # On Docker host, test NFS mount sudo mount -t nfs 192.168.1.100:/srv/docker-data /mnt/test
# Check mount options mount | grep nfs-data
# Test read/write touch /mnt/test/testfile ls -la /mnt/test/
# Unmount test sudo umount /mnt/test
# Check NFS server logs sudo tail -f /var/log/syslog | grep nfs
# Check client NFS stats nfsstat -m ```
### 8. Fix WSL2 volume mount issues
Configure WSL2 for Docker Desktop:
```bash # In WSL2 distribution, edit /etc/wsl.conf sudo nano /etc/wsl.conf
# Add automount configuration [automount] enabled = true options = "metadata,uid=1000,gid=1000"
# options explanation: # - metadata: Enable permission metadata on Windows drives # - uid=1000: Set default owner # - gid=1000: Set default group
# Restart WSL2 # From PowerShell/CMD: wsl --shutdown
# Start Docker Desktop again ```
Use WSL2 integration:
```bash # In Docker Desktop: # Settings > Resources > WSL Integration # Enable integration for your WSL2 distribution
# Access WSL2 home directory from Docker docker run -v /home/username/data:/data myapp
# Access Windows drives (via /mnt) docker run -v /mnt/c/Users/username/data:/data myapp
# Check WSL2 hostname wsl hostname -I
# Use in Docker networking docker run --add-host=host.docker.internal:host-gateway myapp ```
Fix WSL2 permission translation:
```bash # WSL2 translates Windows permissions to Linux # Check translation is working
# On Windows, check folder permissions # Right-click > Properties > Security # Ensure your user has Full Control
# In WSL2, verify permissions visible ls -la /mnt/c/Users/username/data
# If permissions show as 777 or wrong: # 1. Edit /etc/wsl.conf with metadata option # 2. wsl --shutdown # 3. Restart and check again
# Alternative: Use 9P mount options # In /etc/wsl.conf [automount] options = "metadata,uid=1000,gid=1000,umask=022" ```
### 9. Fix Docker Compose volume permissions
Configure Compose volume options:
```yaml # docker-compose.yml version: '3.8'
services: app: image: myapp volumes: # Bind mount with explicit permissions - type: bind source: ./data target: /data bind: propagation: shared selinux: z # or Z for shared # Or named volume - app_data:/var/lib/app/data user: "1000:1000"
volumes: app_data: driver: local driver_opts: type: none device: /srv/docker/app_data o: bind ```
Use init container in Compose:
```yaml # docker-compose.yml version: '3.8'
services: permission-fix: image: alpine volumes: - app_data:/data command: chown -R 1000:1000 /data user: root
app: build: . volumes: - app_data:/data - ./src:/app/src:ro # Read-only source code user: "1000:1000" depends_on: permission-fix: condition: service_completed_successfully
database: image: postgres:15 volumes: - db_data:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: secret
volumes: app_data: db_data: ```
### 10. Monitor and debug volume permissions
Create diagnostic script:
```bash #!/bin/bash # diagnose-volume-permissions.sh
CONTAINER_NAME=$1 VOLUME_PATH=$2
echo "=== Docker Volume Permission Diagnostic ===" echo ""
# Container info echo "Container: $CONTAINER_NAME" docker inspect $CONTAINER_NAME --format 'User: {{.Config.User}}'
# Mount info echo "" echo "Mounts:" docker inspect $CONTAINER_NAME --format '{{json .Mounts}}' | jq '.[] | {Type, Source, Destination, RW}'
# Check host path echo "" echo "Host path permissions:" SOURCE=$(docker inspect $CONTAINER_NAME --format '{{(index .Mounts 0).Source}}') ls -la "$SOURCE"
# Check SELinux echo "" echo "SELinux context:" ls -Z "$SOURCE" 2>/dev/null || echo "SELinux not enabled or not available"
# Test write from container echo "" echo "Testing write access:" docker exec $CONTAINER_NAME touch "$VOLUME_PATH/test-$(date +%s)" 2>&1
# Check result if [ $? -eq 0 ]; then echo "Write test: SUCCESS" else echo "Write test: FAILED" fi
# Show files in volume echo "" echo "Volume contents:" docker exec $CONTAINER_NAME ls -la "$VOLUME_PATH" ```
Enable Docker debug logging:
```bash # Edit Docker daemon configuration sudo nano /etc/docker/daemon.json
# Add debug logging { "debug": true, "log-level": "debug" }
# Restart Docker sudo systemctl restart docker
# Check logs for volume errors sudo journalctl -u docker -f | grep -i volume sudo journalctl -u docker -f | grep -i permission ```
Prevention
- Document UID/GID requirements for each containerized application
- Use Docker volumes instead of bind mounts for production data
- Initialize volume permissions during deployment, not at runtime
- Test volume permissions in CI/CD before production deployment
- Use Docker Compose health checks to verify volume access
- Document SELinux requirements for each volume mount
- Regular audit of volume ownership and permissions
Related Errors
- **EACCES permission denied**: Linux permission or ACL issue
- **SELinux avc denied**: Security context blocking access
- **Read-only file system**: Mount option or NFS export restriction
- **Sharing violated**: Docker Desktop file sharing issue
- **Invalid mount config**: Bind mount path doesn't exist