The EACCES permission denied error occurs when a process inside a Docker container lacks write access to a directory, typically node_modules. This commonly happens due to UID/GID mismatches between the host and container or incorrect ownership after switching users in a Dockerfile.
The EACCES (Error: Access) permission denied error in Docker indicates that the process running inside your container does not have sufficient filesystem permissions to read, write, or execute files in the specified directory. This error is extremely common in Node.js applications running in Docker, particularly when: - You switch to a non-root user with the USER directive but the application directories are still owned by root - You mount a volume from the host where the file ownership doesn't match the container's user ID - npm or yarn tries to create cache directories (like node_modules/.cache) but the user lacks write permissions The error typically appears during npm install, when starting a development server (like webpack-dev-server or Vite), or when the application tries to write to node_modules at runtime.
First, check which user your container process runs as:
# Check the user in a running container
docker exec <container_name> whoami
# Check the user ID and group ID
docker exec <container_name> idThis will show you the UID and GID of the running process, which you'll need to match with file ownership.
Inspect the ownership of the problematic directory:
# Check ownership of node_modules
docker exec <container_name> ls -la /app/
# Check ownership of specific subdirectory
docker exec <container_name> ls -la /app/node_modules/If files are owned by root:root but your process runs as a different user (e.g., node with UID 1000), that's the cause of the permission error.
The most reliable fix is to install dependencies as root and switch users only at the end:
FROM node:20-alpine
WORKDIR /app
# Copy package files and install as root
COPY package*.json ./
RUN npm ci
# Copy application code
COPY . .
# Create non-root user and set ownership
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nodejs -u 1001 -G nodejs \
&& chown -R nodejs:nodejs /app
# Switch to non-root user only for runtime
USER nodejs
CMD ["node", "server.js"]This ensures all files are properly owned by the runtime user.
If you must run npm install as a non-root user, pre-create the directories:
FROM node:20-alpine
# Create user first
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nodejs -u 1001 -G nodejs
WORKDIR /app
# Pre-create node_modules with correct ownership
RUN mkdir -p /app/node_modules \
&& chown -R nodejs:nodejs /app
USER nodejs
COPY --chown=nodejs:nodejs package*.json ./
RUN npm ci
COPY --chown=nodejs:nodejs . .
CMD ["node", "server.js"]The --chown flag on COPY ensures files are owned by the correct user.
When using bind mounts for development, ensure the host directory permissions match:
# docker-compose.yml
services:
app:
build: .
volumes:
- .:/app
- /app/node_modules # Anonymous volume for node_modules
user: "1000:1000" # Match your host user's UID:GIDFind your host UID/GID with:
id -u # UID
id -g # GIDUsing an anonymous volume for node_modules prevents host/container conflicts.
The cleanest solution for development is to use an anonymous volume for node_modules:
services:
app:
build: .
volumes:
- .:/app # Mount source code
- /app/node_modules # Anonymous volume (container's node_modules)This ensures:
- Source code changes sync from host to container
- Container uses its own node_modules (installed during build)
- No permission conflicts between host and container
Understanding UID/GID Mapping
Docker containers on Linux share the host's kernel, so file permissions are based on numeric UIDs/GIDs, not usernames. The "node" user in a container (UID 1000) is the same as UID 1000 on the host, regardless of the username.
Rootless Docker and User Namespaces
If you're running rootless Docker or using user namespaces, UIDs are remapped. The container's UID 1000 might map to UID 101000 on the host. Check your Docker configuration with:
cat /etc/subuid
cat /etc/subgidBuildKit and Cache Mounts
With Docker BuildKit, you can use cache mounts to speed up builds while avoiding permission issues:
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
npm ciSecurity Considerations
Never use chmod 777 as a permanent fix. While it makes the error go away, it allows any user to read, write, and execute files, which is a security risk. Always use proper ownership (chown) instead.
image 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