This error occurs when a Docker container attempts to change file ownership using chown, but the operation is blocked due to volume mount restrictions, filesystem limitations, user namespace remapping, or insufficient permissions. Common fixes include mounting to parent directories, using COPY --chown in Dockerfiles, or adjusting host permissions before container startup.
The "chown: changing ownership of '/path': Operation not permitted" error in Docker indicates that a process inside your container tried to change the owner or group of a file or directory, but the operation was denied by the system. This error is particularly common with: 1. **Bind-mounted volumes**: When you mount a host directory into a container, the container may not have permission to change ownership of files that reside on the host filesystem. 2. **Database containers**: Official images for MongoDB, MySQL, PostgreSQL, and other databases often run entrypoint scripts that attempt to chown data directories for the correct service user. 3. **User namespace remapping**: When Docker uses user namespaces, the UID/GID inside the container are mapped to different values on the host, causing ownership operations to fail. 4. **Filesystem limitations**: Some filesystems (like exFAT, NTFS, or certain network shares) do not support Unix-style ownership and permissions. The error typically appears during container startup when entrypoint scripts try to ensure proper file ownership, or when applications attempt to create and own their data directories.
For database containers like MongoDB, the most common fix is to mount your volume to the parent directory instead of the exact data path:
# docker-compose.yml - WRONG approach
services:
mongodb:
image: mongo:latest
volumes:
- ./data:/data/db # Container can't chown /data/db
# docker-compose.yml - CORRECT approach
services:
mongodb:
image: mongo:latest
volumes:
- ./data:/data # Mount to parent, container creates /data/db itselfThis works because the container's entrypoint script creates the /data/db subdirectory itself and can set ownership on directories it creates.
For MySQL/MariaDB:
services:
mysql:
image: mysql:8
volumes:
- ./mysql-data:/var/lib # Instead of /var/lib/mysqlNote: This approach may require more disk space on the host and changes how data is organized.
Named volumes are managed by Docker and have proper permission handling:
# docker-compose.yml
services:
mongodb:
image: mongo:latest
volumes:
- mongodb_data:/data/db # Named volume works correctly
volumes:
mongodb_data: # Docker manages this volumeOr with docker run:
# Create a named volume
docker volume create mongodb_data
# Use the named volume
docker run -v mongodb_data:/data/db mongo:latestNamed volumes:
- Are stored in Docker's volume directory (/var/lib/docker/volumes/)
- Have proper permissions set by Docker
- Can be initialized with correct ownership during first container start
- Work consistently across all platforms (Linux, macOS, Windows)
To migrate data from a bind mount to a named volume:
# Create volume and copy data
docker volume create mongodb_data
docker run --rm -v ./data:/source -v mongodb_data:/dest alpine cp -a /source/. /dest/Ensure the host directory has the correct ownership before the container starts:
# Find the UID/GID the container expects
# For MongoDB: 999:999 (mongodb user)
# For MySQL: 999:999 (mysql user)
# For PostgreSQL: 999:999 (postgres user)
# Set ownership on host directory
sudo chown -R 999:999 ./data
# Or make it writable by anyone (less secure, for development only)
chmod -R 777 ./dataFor containers that run as a specific user, check the Dockerfile or documentation for the expected UID:
# Inspect the image to find the user
docker inspect mongo:latest --format '{{.Config.User}}'
# Or run a command inside to check
docker run --rm mongo:latest idIn Docker Compose, you can also specify the user:
services:
myapp:
image: myimage
user: "1000:1000"
volumes:
- ./data:/app/dataThen set host permissions to match:
sudo chown -R 1000:1000 ./dataWhen building images, use the --chown flag with COPY or ADD to set ownership during the build:
# Create user first
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Copy with correct ownership
COPY --chown=appuser:appgroup ./src /app/src
COPY --chown=appuser:appgroup package*.json /app/
WORKDIR /app
RUN npm install
USER appuser
CMD ["node", "src/index.js"]Important: The user must exist in the image before using --chown. If the user doesn't exist, the COPY command will silently use root:root.
For numeric UID/GID (works even without creating the user):
FROM alpine
COPY --chown=1000:1000 ./files /app/filesNote: COPY --chown only affects files copied during build. It does not help with mounted volumes at runtime.
When Docker uses user namespace remapping, UIDs inside the container map to different UIDs on the host:
# Check if user namespace remapping is enabled
cat /etc/docker/daemon.json | grep userns-remap
# Check the subuid/subgid mappings
cat /etc/subuid
cat /etc/subgid
# Output example: dockremap:100000:65536With remapping enabled, root (UID 0) inside the container becomes UID 100000 on the host. To fix permission issues:
# Set host directory ownership to the remapped UID range
sudo chown -R 100000:100000 ./dataAlternatively, run the container without user namespace remapping for this specific container:
docker run --userns=host -v ./data:/data myimageOr in Docker Compose:
services:
myapp:
image: myimage
userns_mode: "host"
volumes:
- ./data:/dataSecurity note: Disabling user namespace remapping reduces container isolation. Only do this when necessary.
NFS exports typically use root_squash which maps the root user to nobody, preventing ownership changes:
Option 1: Match container user to NFS share owner
# Check NFS share ownership
ls -ln /nfs/share
# Run container as that user
docker run --user 1000:1000 -v /nfs/share:/data myimageOption 2: Configure NFS server (if you have access)
# Edit /etc/exports on the NFS server
/shared/data *(rw,sync,no_root_squash) # Allows root operations
# Or use specific anonymous mapping
/shared/data *(rw,sync,anonuid=999,anongid=999)
# Apply changes
sudo exportfs -raOption 3: Use an init container to set permissions
services:
init-permissions:
image: alpine
user: root
volumes:
- nfs_data:/data
command: chown -R 999:999 /data
myapp:
image: myimage
depends_on:
- init-permissions
volumes:
- nfs_data:/data
volumes:
nfs_data:
driver: local
driver_opts:
type: nfs
o: addr=nfs-server,rw
device: ":/shared/data"Some official images require root privileges. Consider using alternative images designed for rootless environments:
Bitnami images are designed to run as non-root by default:
services:
mongodb:
image: bitnami/mongodb:latest
volumes:
- ./data:/bitnami/mongodb
environment:
- MONGODB_ROOT_PASSWORD=secret
mysql:
image: bitnami/mysql:latest
volumes:
- ./mysql-data:/bitnami/mysql/data
environment:
- MYSQL_ROOT_PASSWORD=secret
postgresql:
image: bitnami/postgresql:latest
volumes:
- ./pg-data:/bitnami/postgresql
environment:
- POSTGRESQL_PASSWORD=secretBitnami images:
- Run as non-root user (UID 1001) by default
- Don't require chown on startup
- Work better in restricted environments (OpenShift, rootless Docker)
- Have consistent permission handling across platforms
For other applications, look for images with -rootless or -unprivileged variants.
On SELinux-enabled systems (RHEL, CentOS, Fedora), add the appropriate label to volumes:
# For private volumes (single container access)
docker run -v ./data:/data:Z myimage
# For shared volumes (multiple containers)
docker run -v ./data:/data:z myimageIn Docker Compose:
services:
myapp:
image: myimage
volumes:
- ./data:/data:ZTo check if SELinux is the issue:
# Check SELinux status
getenforce
# Check for SELinux denials
sudo ausearch -m avc -ts recent
# Temporarily disable for testing (don't do in production)
sudo setenforce 0To permanently fix SELinux context:
# Set the correct context on the directory
sudo semanage fcontext -a -t svirt_sandbox_file_t "./data(/.*)?"
sudo restorecon -Rv ./dataUnderstanding the chown operation flow in containers:
When a database container starts, the entrypoint script typically:
1. Checks if the data directory exists
2. Creates it if missing
3. Runs chown to ensure the database user owns the directory
4. Starts the database process as that user
The chown fails when the container can't modify ownership of a mounted path.
Dockerfile pattern for handling permissions:
FROM alpine
# Create app user
RUN addgroup -S app && adduser -S app -G app
# Create data directory owned by app user
RUN mkdir -p /data && chown app:app /data
# Set as volume (ownership persists in named volumes)
VOLUME /data
USER app
CMD ["myapp"]Entrypoint script pattern for flexible permissions:
#!/bin/sh
# entrypoint.sh - Run as root, then drop privileges
# Fix permissions if running as root
if [ "$(id -u)" = "0" ]; then
chown -R appuser:appgroup /data 2>/dev/null || true
exec gosu appuser "$@"
fi
exec "$@"Docker Desktop (macOS/Windows) specifics:
Docker Desktop uses a VM with file sharing. The gRPC-FUSE or VirtioFS file sharing backends handle permissions differently:
- Files appear owned by the user running Docker Desktop
- chown operations may silently succeed but have no effect
- Named volumes work correctly because they exist inside the VM
Rootless Docker considerations:
In rootless Docker, the daemon runs as a non-root user, adding another layer of UID mapping:
# Host UID -> Rootless Docker UID -> Container UID
# 1000 -> 0 (root in daemon) -> 0 (root in container)Files created by containers in rootless mode are owned by the user running the Docker daemon, not root.
Kubernetes/OpenShift considerations:
These platforms often enforce:
- runAsNonRoot: true - Containers must run as non-root
- readOnlyRootFilesystem: true - No writes to container filesystem
- Security Context Constraints (SCCs) limiting capabilities
Use init containers to fix volume permissions:
initContainers:
- name: fix-permissions
image: busybox
command: ["sh", "-c", "chown -R 999:999 /data"]
volumeMounts:
- name: data
mountPath: /data
securityContext:
runAsUser: 0image operating system "linux" cannot be used on this platform
How to fix 'image operating system linux cannot be used on this platform' in Docker
manifest unknown: manifest unknown
How to fix 'manifest unknown' in Docker
cannot open '/etc/passwd': Permission denied
How to fix 'cannot open: Permission denied' in Docker
Error response from daemon: failed to create the ipvlan port
How to fix 'failed to create the ipvlan port' in Docker
toomanyrequests: Rate exceeded for anonymous users
How to fix 'Rate exceeded for anonymous users' in Docker Hub