How to fix chmod: changing permissions of '/app/script.sh': Operation not permitted in Docker
This error occurs when Docker containers cannot change file permissions using chmod, typically due to non-root user restrictions, volume mount limitations (NFS/CIFS), or base image constraints. The fix usually involves using COPY --chown in Dockerfiles, switching to root temporarily, or adjusting volume mount options.
Quick fix
Fixes chmod: changing permissions of '/app/script.sh': Operation not permitted
# Instead of:
COPY script.sh /app/script.sh
RUN chmod +x /app/script.sh # This may fail
# Use --chown to set owner and permissions in one step:
COPY --chown=1000:1000 script.sh /app/script.sh
RUN chmod +x /app/script.sh # Now works because user 1000 owns the file
# Or if you know your non-root user name:
COPY --chown=appuser:appuser script.sh /app/script.sh- Use COPY --chown in your Dockerfile
- Switch to root user temporarily in Dockerfile
- Set permissions before copying into container
- Handle NFS and network volume mounts
# Instead of:COPY script.sh /app/script.shRUN chmod +x /app/script.sh # This may fail# Use --chown to set owner and permissions in one step:COPY --chown=1000:1000 script.sh /app/script.shRUN chmod +x /app/script.sh # Now works because user 1000 owns the file# Or if you know your non-root user name:COPY --chown=appuser:appuser script.sh /app/script.shchmod: changing permissions of '/app/script.sh': Operation not permitted
What this error means
The "chmod: changing permissions: Operation not permitted" error in Docker indicates that the container process lacks the necessary privileges to modify file permissions. This is different from a standard permission denied error - it means the operation itself is not allowed, regardless of the file's current ownership. This error commonly occurs in three scenarios: 1. **Non-root user in container**: Many Docker images (especially security-hardened ones) run as non-root users by default. When you COPY files into the container, they're owned by root with restricted permissions. The non-root user cannot chmod these files. 2. **Volume mount restrictions**: When mounting volumes from certain filesystems (NFS with root_squash, CIFS/Azure Files, or some network storage), the underlying filesystem doesn't support Linux permission changes. chmod operations are silently ignored or return EPERM. 3. **Read-only layers**: In some cases, files copied during the build process are in read-only layers, and attempting to chmod them at runtime fails. Understanding which scenario applies to your case is crucial for selecting the right fix.
Typical symptoms
- chmod commands fail with 'Operation not permitted' error
- Dockerfile RUN chmod +x commands fail during build
- Container entrypoint scripts fail to execute due to permission issues
- Application startup fails when trying to make files executable
- CI/CD pipelines fail at chmod steps in Docker builds
- Files copied into container cannot have permissions modified
- chown commands also fail with similar errors
Common causes
- Container running as non-root user without ownership of the target files
- Files were COPY'd as root but container runs as unprivileged user
- Volume mounted from NFS with root_squash enabled
- Volume mounted from CIFS/Azure Files which doesn't support Linux permissions
- Base image switches to non-root user before your chmod command
- Docker Desktop on macOS with file sharing permission restrictions
- Read-only volume mounts preventing any file modifications
- SELinux or AppArmor policies blocking permission changes
How to fix "chmod: changing permissions of '/app/script.sh': Operation not permitted"
The most reliable fix is to set ownership when copying files, rather than running chmod afterward:
# Instead of:
COPY script.sh /app/script.sh
RUN chmod +x /app/script.sh # This may fail
# Use --chown to set owner and permissions in one step:
COPY --chown=1000:1000 script.sh /app/script.sh
RUN chmod +x /app/script.sh # Now works because user 1000 owns the file
# Or if you know your non-root user name:
COPY --chown=appuser:appuser script.sh /app/script.shFor multiple files:
# Copy entire directory with correct ownership
COPY --chown=node:node ./src /app/src
# Or use a specific UID that matches your runtime user
COPY --chown=1001:1001 . /appNote: The --chown flag requires Docker 17.09 or later.
If you can't use --chown, switch to root before chmod, then switch back:
FROM node:18-alpine
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Copy files as root (default)
COPY entrypoint.sh /app/entrypoint.sh
# As root, set permissions
RUN chmod +x /app/entrypoint.sh && \
chown appuser:appgroup /app/entrypoint.sh
# Switch to non-root user for runtime
USER appuser
ENTRYPOINT ["/app/entrypoint.sh"]For images that already set a non-root user:
FROM bitnami/node:18
# Temporarily switch to root
USER root
COPY custom-script.sh /opt/scripts/
RUN chmod +x /opt/scripts/custom-script.sh
# Switch back to the original non-root user
USER 1001
CMD ["/opt/scripts/custom-script.sh"]Tip: Check your base image documentation to find the correct non-root user UID to switch back to.
Set the correct permissions on your host before building:
# Make script executable on host before build
chmod +x ./scripts/entrypoint.sh
chmod +x ./scripts/*.sh
# Then build - permissions are preserved
docker build -t myapp .In your Dockerfile, the COPY will preserve the executable bit:
# File already has +x on host, so it's executable in container
COPY entrypoint.sh /app/entrypoint.shFor Git repositories: Git tracks executable permissions, so commit with:
# Add executable permission to Git
git update-index --chmod=+x scripts/entrypoint.sh
git commit -m "Make entrypoint.sh executable"Note: This only works for new COPY operations, not for files in mounted volumes.
NFS and CIFS mounts often don't support Linux permission changes. Use these workarounds:
Option 1: Match container user to volume owner
# Find the UID/GID that owns files on the NFS share
ls -ln /mnt/nfs/share
# Output: drwxr-xr-x 2 1000 1000 4096 Jan 1 12:00 data
# Run container as that user
docker run --user 1000:1000 -v /mnt/nfs/share:/data myappOption 2: Use named volumes instead of bind mounts
# Named volumes handle permissions better
docker volume create app-data
docker run -v app-data:/data myappOption 3: For Azure Files / CIFS, set permissions at mount time
In docker-compose.yml:
services:
app:
image: myapp
volumes:
- type: volume
source: azure-files
target: /data
volume:
nocopy: true
volumes:
azure-files:
driver: local
driver_opts:
type: cifs
o: "username=USER,password=PASS,uid=1000,gid=1000,file_mode=0755,dir_mode=0755"
device: "//storage.file.core.windows.net/share"The file_mode and dir_mode options set permissions at mount time since chmod won't work.
Docker Desktop on macOS has specific file sharing requirements:
Step 1: Ensure directories are shared in Docker Desktop settings:
1. Open Docker Desktop
2. Go to Settings > Resources > File Sharing
3. Add the directories containing your scripts
4. Click Apply & Restart
Step 2: For persistent chmod issues, copy files instead of mounting:
# Instead of mounting at runtime:
# docker run -v ./scripts:/app/scripts myapp
# Copy scripts into the image at build time:
COPY --chmod=755 scripts/ /app/scripts/Step 3: Use Docker volumes for data that needs modification:
# Create a Docker volume (not a bind mount)
docker volume create app-scripts
# Copy files into the volume using a helper container
docker run --rm -v app-scripts:/target -v $(pwd)/scripts:/source alpine sh -c "cp -r /source/* /target/ && chmod +x /target/*.sh"
# Now use the volume in your app
docker run -v app-scripts:/app/scripts myappNote: The --chmod flag in COPY requires BuildKit. Enable it with:
DOCKER_BUILDKIT=1 docker build -t myapp .On RHEL, CentOS, and Fedora systems with SELinux, add the :z or :Z suffix to volume mounts:
# Shared volume (multiple containers can access)
docker run -v /host/scripts:/app/scripts:z myapp
# Private volume (only this container)
docker run -v /host/scripts:/app/scripts:Z myappThe :z/:Z options relabel the volume with appropriate SELinux contexts.
Check if SELinux is the cause:
# Check SELinux status
getenforce
# Look for SELinux denials in audit log
sudo ausearch -m avc -ts recent | grep docker
# Temporarily disable to test (not for production)
sudo setenforce 0
docker run -v /host/scripts:/app/scripts myapp
sudo setenforce 1Permanent fix without :z/:Z:
# Set the correct SELinux context on host directory
sudo chcon -Rt svirt_sandbox_file_t /host/scriptsDocker BuildKit supports setting permissions directly in COPY:
# syntax=docker/dockerfile:1
FROM alpine
# Set executable permission directly in COPY
COPY --chmod=755 entrypoint.sh /app/entrypoint.sh
COPY --chmod=644 config.json /app/config.json
# Multiple files with same permissions
COPY --chmod=755 scripts/*.sh /app/scripts/Enable BuildKit:
# Option 1: Environment variable
DOCKER_BUILDKIT=1 docker build -t myapp .
# Option 2: Docker daemon config (/etc/docker/daemon.json)
{
"features": {
"buildkit": true
}
}
# Option 3: Use docker buildx (BuildKit by default)
docker buildx build -t myapp .Combining --chown and --chmod:
# Set both owner and permissions
COPY --chown=node:node --chmod=755 entrypoint.sh /app/entrypoint.shNote: --chmod in COPY requires Docker 20.10+ with BuildKit enabled.
Advanced notes
Understanding file ownership in multi-stage builds:
In multi-stage builds, file ownership can be tricky:
# Stage 1: Build as root
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Runtime as non-root
FROM node:18-alpine
RUN adduser -D appuser
WORKDIR /app
# Files from builder stage are owned by root
# Use --chown to transfer with correct ownership
COPY --from=builder --chown=appuser:appuser /app/dist ./dist
COPY --from=builder --chown=appuser:appuser /app/node_modules ./node_modules
USER appuser
CMD ["node", "dist/index.js"]Debugging permission issues:
# Check file ownership and permissions inside container
docker run --rm myapp ls -la /app/
# Check what user the container runs as
docker run --rm myapp id
# Check if filesystem supports permission changes
docker run --rm -v /path:/data myapp stat -f /dataRootless Docker considerations: In rootless Docker, UID mapping adds complexity. The container's root user maps to your host user, which can cause unexpected permission behaviors with bind mounts.
Kubernetes equivalent: In Kubernetes, use securityContext to manage user/group:
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000 # Sets group ownership of mounted volumesBest practices:
1. Always use COPY --chown for files that non-root users need to access
2. Prefer named volumes over bind mounts when permissions are problematic
3. Set file permissions on the host before building when possible
4. Use BuildKit's --chmod for cleaner Dockerfiles
5. Document the expected user/group IDs in your project's README
Frequently asked questions
- What does "chmod: changing permissions of '/app/script.sh': Operation not permitt" mean?
- The "chmod: changing permissions: Operation not permitted" error in Docker indicates that the container process lacks the necessary privileges to modify file permissions. This is different from a standard permission denied error - it means the operation itself is not allowed, regardless of the file's current ownership. This error commonly occurs in three scenarios: 1.
- How do I fix "chmod: changing permissions of '/app/script.sh': Operation not permitt"?
- This error occurs when Docker containers cannot change file permissions using chmod, typically due to non-root user restrictions, volume mount limitations (NFS/CIFS), or base image constraints. The fix usually involves using COPY --chown in Dockerfiles, switching to root temporarily, or adjusting volume mount options.
- What causes "chmod: changing permissions of '/app/script.sh': Operation not permitt"?
- Container running as non-root user without ownership of the target files Files were COPY'd as root but container runs as unprivileged user Volume mounted from NFS with root_squash enabled
- Is the "chmod: changing permissions of '/app/script.sh': Operation not permitt" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚Docker Official Documentation
- 💬
- 💬
- 💬Stack Overflow: Docker Apache chmod Operation not permitted(Stack Overflow)
- 📝
- 📝