How to fix chmod: changing permissions of '/app/script.sh': Operation not permitted in Docker

DockerINTERMEDIATEMEDIUMUpdated Dec 6, 2025· 394 views

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
  1. Use COPY --chown in your Dockerfile
  2. Switch to root user temporarily in Dockerfile
  3. Set permissions before copying into container
  4. Handle NFS and network volume mounts
docker — error
# 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
chmod: 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

  1. Container running as non-root user without ownership of the target files
  2. Files were COPY'd as root but container runs as unprivileged user
  3. Volume mounted from NFS with root_squash enabled
  4. Volume mounted from CIFS/Azure Files which doesn't support Linux permissions
  5. Base image switches to non-root user before your chmod command
  6. Docker Desktop on macOS with file sharing permission restrictions
  7. Read-only volume mounts preventing any file modifications
  8. SELinux or AppArmor policies blocking permission changes

How to fix "chmod: changing permissions of '/app/script.sh': Operation not permitted"

1Use COPY --chown in your Dockerfile

The most reliable fix is to set ownership when copying files, rather than running chmod afterward:

dockerfile
# 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

For multiple files:

dockerfile
# 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 . /app

Note: The --chown flag requires Docker 17.09 or later.

2Switch to root user temporarily in Dockerfile

If you can't use --chown, switch to root before chmod, then switch back:

dockerfile
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:

dockerfile
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.

3Set permissions before copying into container

Set the correct permissions on your host before building:

bash
# 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:

dockerfile
# File already has +x on host, so it's executable in container
COPY entrypoint.sh /app/entrypoint.sh

For Git repositories: Git tracks executable permissions, so commit with:

bash
# 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.

4Handle NFS and network volume mounts

NFS and CIFS mounts often don't support Linux permission changes. Use these workarounds:

Option 1: Match container user to volume owner

bash
# 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 myapp

Option 2: Use named volumes instead of bind mounts

bash
# Named volumes handle permissions better
docker volume create app-data
docker run -v app-data:/data myapp

Option 3: For Azure Files / CIFS, set permissions at mount time

In docker-compose.yml:

yaml
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.

5Fix Docker Desktop macOS permission issues

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:

dockerfile
# 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:

bash
# 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 myapp

Note: The --chmod flag in COPY requires BuildKit. Enable it with:

bash
DOCKER_BUILDKIT=1 docker build -t myapp .
6Handle SELinux volume restrictions

On RHEL, CentOS, and Fedora systems with SELinux, add the :z or :Z suffix to volume mounts:

bash
# 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 myapp

The :z/:Z options relabel the volume with appropriate SELinux contexts.

Check if SELinux is the cause:

bash
# 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 1

Permanent fix without :z/:Z:

bash
# Set the correct SELinux context on host directory
sudo chcon -Rt svirt_sandbox_file_t /host/scripts
7Use COPY --chmod with BuildKit

Docker BuildKit supports setting permissions directly in COPY:

dockerfile
# 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:

bash
# 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:

dockerfile
# Set both owner and permissions
COPY --chown=node:node --chmod=755 entrypoint.sh /app/entrypoint.sh

Note: --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:

dockerfile
# 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:

bash
# 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 /data

Rootless 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:

yaml
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000  # Sets group ownership of mounted volumes

Best 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