Questions
A container can't write to its mounted volume. Fix the permission issue.
The Scenario
You’re running a Node.js application that needs to write logs to a mounted volume:
$ docker run -d \
--name api \
--user 1001:1001 \
-v /data/logs:/app/logs \
api:latest
$ docker logs api
Error: EACCES: permission denied, open '/app/logs/app.log'
The container runs as a non-root user (security best practice), but now it can’t write to the mounted volume.
The Challenge
Fix the permission issue while maintaining security best practices. Explain the relationship between container user IDs and host filesystem permissions.
A junior engineer might remove the --user flag to run as root, chmod 777 the host directory, run chmod inside the container hoping it helps, or just use a different path. Running as root defeats security hardening, chmod 777 is a security vulnerability, chmod inside the container doesn't affect mounted volumes, and changing paths doesn't solve the underlying issue.
A senior engineer understands that the container's UID must match or have permission to access the host directory. Solutions include: setting correct ownership on the host, using named volumes instead of bind mounts, or running an init container to fix permissions. The key insight is that UIDs, not usernames, determine access rights across the container-host boundary.
Understanding the Problem
# Check who owns the host directory
ls -la /data/logs
# drwxr-xr-x 2 root root 4096 Jan 15 10:00 /data/logs
# Container runs as UID 1001, but directory is owned by root
# UID 1001 has no write permissionKey Insight: Docker doesn’t map usernames between host and container. It uses UIDs directly. A user named nodejs with UID 1001 in the container is treated as UID 1001 on the host, regardless of what that UID is called on the host.
Solution 1: Fix Host Directory Ownership
# Create directory with correct ownership
sudo mkdir -p /data/logs
sudo chown 1001:1001 /data/logs
# Verify
ls -la /data/
# drwxr-xr-x 2 1001 1001 4096 Jan 15 10:00 logs
# Now the container can write
docker run -d \
--name api \
--user 1001:1001 \
-v /data/logs:/app/logs \
api:latestSolution 2: Use Named Volumes (Recommended)
# Create a named volume
docker volume create app-logs
# Run container with named volume
docker run -d \
--name api \
--user 1001:1001 \
-v app-logs:/app/logs \
api:latestWhy named volumes are better:
- Docker manages permissions automatically
- Portable across hosts
- Easier backup and migration
- Better for production workloads
Solution 3: Init Container Pattern
In Docker Compose, use an init container to fix permissions:
version: '3.8'
services:
# Init container fixes permissions
init-permissions:
image: busybox
command: chown -R 1001:1001 /data
volumes:
- app-data:/data
user: root
# Application container
api:
image: api:latest
user: "1001:1001"
volumes:
- app-data:/app/logs
depends_on:
init-permissions:
condition: service_completed_successfully
volumes:
app-data:Solution 4: Match UIDs in Dockerfile
FROM node:18-alpine
# Use a UID that matches your host system
ARG UID=1001
ARG GID=1001
RUN addgroup -g ${GID} -S nodejs && \
adduser -S nodejs -u ${UID} -G nodejs
WORKDIR /app
COPY --chown=nodejs:nodejs . .
USER nodejs
CMD ["node", "server.js"]Build with matching UID:
# Build with UID that matches host directory owner
docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t api:latest . Permission Debugging Commands
# Check container's user
docker exec api id
# uid=1001(nodejs) gid=1001(nodejs)
# Check mounted directory permissions inside container
docker exec api ls -la /app/logs
# Check host directory permissions
ls -la /data/logs
# Test write access
docker exec api touch /app/logs/test.txt
# Check volume mount details
docker inspect api --format='{{json .Mounts}}' | jq
Common Permission Scenarios
| Scenario | Host Owner | Container User | Result | Fix |
|---|---|---|---|---|
| Default | root (0) | root (0) | Works | - |
| Secure container | root (0) | 1001 | Fails | chown 1001 on host |
| Named volume | Docker | 1001 | Works | - |
| SELinux enabled | root (0) | 1001 | Fails | Add :z or :Z suffix |
SELinux/AppArmor Considerations
On systems with SELinux (RHEL/CentOS) or AppArmor:
# Add :z for shared volume (multiple containers)
docker run -v /data/logs:/app/logs:z api:latest
# Add :Z for private volume (single container)
docker run -v /data/logs:/app/logs:Z api:latest
Warning: :Z relabels the host directory. Don’t use it on system directories!
Practice Question
A container running as UID 1001 can't write to a bind-mounted directory owned by root. What's the most secure fix?