Claude Code Docker Permission Denied (2026)
Understanding and Fixing Docker Permission Denied Bind Mount Errors in Claude Code
When using Claude Code to work with Docker containers, you may encounter the frustrating “permission denied” error when trying to access bind-mounted directories. This issue commonly arises when Docker containers need to read from or write to host directories, and understanding how to resolve it is essential for smooth development workflows with Claude Code.
What Causes Docker Bind Mount Permission Errors
Docker bind mounts allow you to share directories between your host system and containers. However, permission denied errors occur when the user inside the container lacks the necessary permissions to access the mounted directory on the host.
The full error typically looks like one of these:
permission denied: open '/app/output/result.csv'
mkdir /workspace/data: permission denied
open /mnt/host/config.json: permission denied: unknown
These errors typically happen because:
- UID/GID Mismatch: The container user (often root or a specific UID like 1000) doesn’t match the host user’s ownership of the mounted directory
- Read-Only Mounts: The mount was created with
:ro(read-only) permissions but the container is trying to write - SELinux/AppArmor Restrictions: Security modules on the host may prevent container access to certain paths
- Missing Parent Directory: The target path inside the container doesn’t exist, and the container user can’t create it
- macOS File Sharing Not Enabled: On Docker Desktop for Mac, directories outside the default shared paths require explicit sharing configuration
When Claude Code executes Docker commands through its agentic workflow, writing files, reading project configs, or processing outputs, these permission issues interrupt the loop. Claude may retry the command, produce confusing error messages, or silently fail depending on how your setup handles the error.
Diagnosing the Problem
Before applying a fix, identify the actual cause. Run these commands to gather information:
Check your host user's UID and GID
id
Example output: uid=1000(mike) gid=1000(mike) groups=1000(mike),4(adm),27(sudo)
Check the ownership of the directory you're trying to mount
ls -la /path/to/your/directory
Look at the owner and group
Check what user is running inside the container
docker run --rm myimage id
Example output: uid=0(root) gid=0(root) groups=0(root)
Inspect a running container's user
docker inspect container_name | grep -i user
If your host user is UID 1000 but the container runs as root (UID 0) or a different UID, that is the mismatch causing the error.
On Linux, you can also check if SELinux is enforcing:
getenforce
Returns: Enforcing, Permissive, or Disabled
If it returns “Enforcing,” you need the :z or :Z flag on your bind mount (covered below).
Solution 1: Using Named Volumes Instead of Bind Mounts
Named volumes are managed by Docker and automatically handle permission issues. Create a volume instead of a bind mount:
Create a named volume
docker volume create my-project-data
Use the volume in your container
docker run -v my-project-data:/app myimage
This approach works well for persistent data but doesn’t give you direct access to host directories. If Claude Code needs to read your actual project files from the host filesystem, named volumes alone won’t solve the problem, you need to copy the files in or use a bind mount with corrected permissions.
A hybrid approach: use a named volume for outputs (so the container can write freely) and a read-only bind mount for source files (which the container only needs to read):
docker run \
-v $(pwd)/src:/app/src:ro \
-v build-output:/app/output \
myimage
Solution 2: Adjusting Container User Permissions
You can run containers with your host user’s UID and GID to match ownership:
Run container with host user permissions
docker run -v $(pwd):/app -u $(id -u):$(id -g) myimage
This tells Docker to run the container process as your host user, eliminating permission mismatches. The $(id -u) and $(id -g) subshell commands expand to your actual numeric UID and GID at runtime.
One caveat: some containers break when run as a non-root user because they need to write to /root, install packages, or access files that belong to root. If you get secondary errors after applying this fix, the container image itself may need modification (see Solution 6).
You can also hardcode a specific UID instead of using subshell expansion, which is useful in scripts:
docker run -v $(pwd):/app -u 1000:1000 myimage
Solution 3: Fixing Permissions After Container Start
If the container is already running, you can fix permissions from inside:
Access container shell
docker exec -it container_name /bin/bash
Fix ownership (run this as root inside the container)
chown -R 1000:1000 /app
Or set permissive permissions (less secure, use only for development)
chmod -R 755 /app
However, this requires manual intervention and won’t persist across container recreations. For development workflows where Claude Code frequently rebuilds or restarts containers, this approach creates a repetitive manual step. Use it for one-off debugging, not as a permanent fix.
An alternative: use an entrypoint script that fixes permissions automatically on container start:
#!/bin/bash
entrypoint.sh
Fix bind mount ownership before starting the main process
chown -R appuser:appuser /app
exec gosu appuser "$@"
This requires the gosu utility in your image and a Dockerfile that sets it up:
FROM python:3.11-slim
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
RUN useradd -m -u 1000 appuser
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["python", "main.py"]
Solution 4: Using Docker Compose with User Configuration
Docker Compose simplifies permission handling with the user directive:
version: '3.8'
services:
app:
image: myimage
volumes:
- .:/app
user: "${UID}:${GID}"
environment:
- UID=${UID}
- GID=${GID}
Run with environment variables set:
UID=$(id -u) GID=$(id -g) docker-compose up -d
For a more permanent setup so you don’t have to export variables every time, create a .env file in the same directory as your docker-compose.yml:
.env
UID=1000
GID=1000
Docker Compose reads .env automatically. This makes the user configuration version-controllable and shareable with your team (though be careful not to commit secrets in the same file).
A complete production-ready Compose configuration for a Claude Code development workflow:
version: '3.8'
services:
dev:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/workspace
- node_modules:/workspace/node_modules
- pip_cache:/root/.cache/pip
user: "${UID:-1000}:${GID:-1000}"
working_dir: /workspace
environment:
- HOME=/tmp
stdin_open: true
tty: true
volumes:
node_modules:
pip_cache:
The ${UID:-1000} syntax provides a fallback value of 1000 if the variable isn’t set, preventing Compose from failing when the variable is undefined.
Solution 5: SELinux and AppArmor Label Fixes
On Linux systems running SELinux (common on RHEL, Fedora, and CentOS), you need to add a label flag to your bind mount:
:z - shared label, allows multiple containers to access the volume
docker run -v $(pwd):/app:z myimage
:Z - private label, only this container can access the volume
docker run -v $(pwd):/app:Z myimage
The difference matters: :z is for shared volumes that multiple containers access simultaneously. :Z is for volumes used by a single container. Using :Z on a shared volume will cause other containers to lose access.
You can verify SELinux labels were applied:
ls -Z /path/to/directory
Should show: system_u:object_r:container_file_t:s0
For AppArmor on Ubuntu and Debian systems, the fix is different. Check if a profile is restricting the container:
aa-status | grep docker
If a profile is loaded for your container, you may need to modify it or run in unconfined mode for development:
docker run --security-opt apparmor=unconfined -v $(pwd):/app myimage
Use the unconfined option only in development, never in production.
Solution 6: Fixing Permissions in the Dockerfile
The most maintainable long-term solution is to fix permissions at image build time. Create a non-root user in your Dockerfile with a UID that matches your typical host user:
FROM python:3.11-slim
Create a non-root user with UID 1000
RUN groupadd -g 1000 appgroup && \
useradd -u 1000 -g appgroup -m -s /bin/bash appuser
WORKDIR /app
Copy files with correct ownership
COPY --chown=appuser:appgroup requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=appuser:appgroup . .
Switch to non-root user
USER appuser
CMD ["python", "main.py"]
When you build and run this image with a bind mount, the container’s appuser (UID 1000) will match your host user (assuming you’re also UID 1000, which is the default on most single-user Linux systems).
Check your UID with id -u, if it’s not 1000, adjust the ARG pattern:
FROM python:3.11-slim
ARG USER_UID=1000
ARG USER_GID=1000
RUN groupadd -g ${USER_GID} appgroup && \
useradd -u ${USER_UID} -g appgroup -m appuser
USER appuser
Build with your actual UID:
docker build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) -t myimage .
Solution 7: Creating a Custom Claude Code Skill for Docker Permissions
You can create a Claude Code skill that automatically handles Docker permission issues. Create a file at ~/.claude/skills/docker-permission-fixer/skill.md:
---
name: docker-permission-fixer
description: Fix Docker bind mount permission issues automatically
---
Docker Permission Fixer
This skill helps diagnose and fix Docker bind mount permission issues.
Available Actions
1. Check Mount Permissions - Analyzes current container mounts and identifies permission problems
2. Fix with User Flag - Suggests running containers with appropriate -u flag
3. Generate Compose Config - Creates Docker Compose configurations with proper user settings
Usage Examples
When you encounter "permission denied" errors with bind mounts:
- Run "analyze my container permissions" to diagnose the issue
- Run "generate compose file for current directory" to create a permission-safe configuration
This skill can then provide contextual help when Claude Code detects Docker permission errors.
Preventing Permission Issues in Claude Code Workflows
When working with Claude Code and Docker, follow these best practices to avoid hitting permission errors in the first place:
- Always specify user permissions in Dockerfiles: Add a
USERdirective with a non-root user that matches your typical development UID - Use consistent UIDs: Define a specific UID in your Dockerfile (1000 is the convention) and ensure it matches the host user for local development
- Set up volume permissions in Compose: Use the
userfield in Docker Compose files so all team members get the same behavior - Consider Docker Socket permissions: The Docker socket (
/var/run/docker.sock) is owned by root or thedockergroup, your host user must be in thedockergroup, or Claude Code’s Docker commands will fail with permission errors - Add your user to the docker group on Linux:
sudo usermod -aG docker $USER(requires logout/login to take effect) - Test bind mounts before handing off to Claude Code: Run a quick write test manually before letting Claude Code work inside the container
Common Scenarios and Solutions at a Glance
| Scenario | Root Cause | Fix |
|---|---|---|
| Container can’t read source files | UID mismatch | -u $(id -u):$(id -g) flag |
| Container can’t write build outputs | UID mismatch or read-only mount | -u flag or remove :ro |
| SELinux blocking mount | Missing file label | Add :z or :Z to volume spec |
| Docker Desktop Mac issue | Directory not shared | Add path in Docker Desktop settings |
| Works on host but not in CI | CI runner has different UID | Use --build-arg to set UID at build time |
| Docker socket not accessible | User not in docker group | sudo usermod -aG docker $USER |
Common Scenarios and Solutions
Scenario: Claude Code Can’t Read Project Files in Container
Problem: Your container can’t read source files mounted from the host.
Solution: Ensure the mounted directory is readable and owned correctly:
Make directory readable by all users
chmod -R 755 /path/to/project
If ownership is wrong, fix it first
chown -R $(id -u):$(id -g) /path/to/project
Then run with matching UID
docker run -v /path/to/project:/app -u $(id -u):$(id -g) myimage
Scenario: Container Can’t Write Build Outputs
Problem: Build artifacts can’t be written to mounted directories. Common with compiled languages or any workflow that generates output files.
Solution: Use the -u flag with matching UID/GID:
docker run -v $(pwd)/output:/output -u $(id -u):$(id -g) build-image
If the output directory doesn’t exist yet on the host, create it first:
mkdir -p $(pwd)/output
docker run -v $(pwd)/output:/output -u $(id -u):$(id -g) build-image
Docker cannot create the host directory for you, and without it the mount fails silently or with a permission error.
Scenario: Docker-in-Docker with Claude Code
Problem: Running Docker inside containers requires socket access. This comes up when Claude Code is itself running inside a container and needs to spin up additional containers.
Solution: Mount the Docker socket with appropriate permissions:
docker run -v /var/run/docker.sock:/var/run/docker.sock -u $(id -u):$(id -g) docker-image
This grants the container full Docker access on the host, use only with trusted images. The container can control the host Docker daemon completely, which is a significant security boundary to cross.
Scenario: Permission Errors Only in CI/CD
Problem: Everything works locally but the pipeline fails with permission errors.
Cause: Your local user is UID 1000, but the CI runner may use a different UID (often 0 for root, or a service account with a high UID like 999).
Solution: Pass the runner’s UID as a build argument and use it consistently:
GitHub Actions example
- name: Build image
run: |
docker build \
--build-arg USER_UID=$(id -u) \
--build-arg USER_GID=$(id -g) \
-t myimage .
Quick Reference Commands
One-liner to run any container with your current user’s permissions:
docker run -it --rm -v "$(pwd)":/workspace -w /workspace -u $(id -u):$(id -g) <image> <command>
One-liner to inspect what user a running container is using:
docker exec container_name id
One-liner to fix ownership of a mounted directory from outside the container:
sudo chown -R $(id -u):$(id -g) ./mounted-directory
Check if your user is in the docker group (Linux):
groups | grep docker
These commands cover the most common diagnostic and remediation steps. Bookmark them if you work regularly with Docker in development, you will use them repeatedly.
Conclusion
Docker bind mount permission errors are common but fully solvable once you understand the root cause. In most cases it comes down to a UID/GID mismatch between the host and the container. The fastest fix for development is the -u $(id -u):$(id -g) flag. The most maintainable long-term fix is building images with a matching non-root user baked in via the Dockerfile.
For Claude Code integration specifically, getting permissions right upfront means Claude can read and write project files, generate outputs, and iterate without hitting dead ends mid-workflow. If you are setting up a containerized Claude Code development environment for the first time, start with the Docker Compose approach (Solution 4) and the Dockerfile non-root user (Solution 6) together, that combination handles the majority of real-world cases.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Troubleshooting Guide. Diagnose and fix any Claude Code issue
- Claude Code for Beginners: Complete Getting Started Guide
- Claude Code Not Working After Update: How to Fix
- Best Claude Skills for Developers in 2026
- Claude Code Troubleshooting Hub
- Fix Claude Code NPM Install Eacces Permission — Quick Guide
Built by theluckystrike. More at zovo.one
Get started → Generate your project setup with our Project Starter.
Configure permissions → Build your settings with our Permission Configurator.