The EACCES permission denied unlink error occurs when npm or another process inside a Docker container cannot delete a file due to insufficient permissions. This typically happens when files were created by a different user (often root) than the current process user, or when volume mounts have ownership mismatches.
The EACCES (Error: Access) permission denied error with the "unlink" operation specifically indicates that a process inside your Docker container is trying to delete a file but lacks the necessary filesystem permissions to do so. The "unlink" system call is how files are deleted in Unix-based systems. When you see this error with package-lock.json or other npm-related files, it usually means: 1. **Root vs non-root user mismatch**: Files in your container were created by the root user (during docker build), but your application runs as a non-root user (like 'node') and cannot delete them. 2. **Volume mount permissions**: You have a bind mount from your host system where the files are owned by your host user ID, which doesn't match the container's user ID. 3. **npm install attempting cleanup**: npm tries to delete old package-lock.json before writing a new one, but the current user cannot delete the existing file. This error is particularly common in Node.js development workflows where developers mount their project directory into the container for hot reloading.
First, check who owns the file that cannot be unlinked and compare it to the running user:
# Check the running user
docker exec <container_name> whoami
docker exec <container_name> id
# Check ownership of the problematic file
docker exec <container_name> ls -la /app/package-lock.json
# Check parent directory ownership
docker exec <container_name> ls -la /app/If the file shows root:root ownership but your process runs as node (UID 1000), that's your problem.
Ensure all copied files are owned by the correct user from the start:
FROM node:20-alpine
# Create app directory and user
WORKDIR /app
# Copy package files with correct ownership
COPY --chown=node:node package*.json ./
# Install dependencies as root (will be fast, no permission issues)
RUN npm ci
# Copy rest of application with correct ownership
COPY --chown=node:node . .
# Fix ownership of any generated files (like node_modules)
RUN chown -R node:node /app
# Switch to non-root user for runtime
USER node
CMD ["node", "server.js"]The --chown=node:node flag ensures package.json and package-lock.json are owned by the node user.
A simpler approach is to do all npm operations as root, then switch users only for runtime:
FROM node:20-alpine
WORKDIR /app
# Copy and install as root
COPY package*.json ./
RUN npm ci
# Copy application code
COPY . .
# Change ownership of everything to node user
RUN chown -R node:node /app
# Switch to non-root user only for CMD
USER node
CMD ["npm", "start"]This ensures npm never runs into permission issues during build, and the final ownership change allows the application to run as non-root.
When using bind mounts for development, ensure the container user matches your host user:
# docker-compose.yml
services:
app:
build: .
volumes:
- .:/app
- /app/node_modules # Keep container's node_modules separate
user: "${UID:-1000}:${GID:-1000}"Set the environment variables before running:
# On your host machine
export UID=$(id -u)
export GID=$(id -g)
docker compose upAlternatively, create a .env file:
UID=1000
GID=1000Isolate node_modules from host-container conflicts with an anonymous volume:
# docker-compose.yml
services:
app:
build: .
volumes:
- .:/app # Your source code
- /app/node_modules # Anonymous volume - container's own node_modules
- /app/package-lock.json # Also isolate package-lock.json if neededThis prevents the host's file ownership from affecting the container's node_modules and lock files.
To rebuild with fresh node_modules:
docker compose down -v # Remove volumes
docker compose up --buildIf you're stuck with corrupted permissions, do a complete clean rebuild:
# Stop containers and remove volumes
docker compose down -v
# Remove any local node_modules if mounted
rm -rf node_modules
# Rebuild from scratch
docker compose build --no-cache
docker compose upFor individual containers:
docker stop <container>
docker rm -v <container> # -v removes anonymous volumes
docker build --no-cache -t myapp .
docker run myappAs a quick fix for a running container, you can change ownership manually:
# Run as root to fix permissions
docker exec -u root <container_name> chown -R node:node /app
# Or fix just the specific file
docker exec -u root <container_name> chown node:node /app/package-lock.jsonNote: This is a temporary fix. The proper solution is to update your Dockerfile to prevent the issue from recurring.
Understanding the unlink System Call
The "unlink" operation removes a directory entry for a file. To unlink a file, you need write permission on the directory containing the file, not necessarily on the file itself. However, if the directory has the sticky bit set, you must also own the file.
UID/GID Mapping in Docker
On Linux, Docker containers share the host kernel, so file permissions are based on numeric UIDs/GIDs. The "node" user in official Node.js images has UID 1000. If your host user also has UID 1000, you won't have permission issues with bind mounts.
Check your host UID:
id -u # Your user ID
id -g # Your group IDWSL2-Specific Considerations
On Windows with WSL2, file permissions can behave unexpectedly. If you're developing on WSL2:
- Delete node_modules from the Windows file explorer, not from within WSL
- Keep your project files in the WSL filesystem (/home/user/) rather than in /mnt/c/
- Consider using named volumes instead of bind mounts for node_modules
Multi-stage Builds
For production images, use multi-stage builds to avoid permission issues entirely:
# Build stage - run as root, no permission concerns
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage - minimal, secure
FROM node:20-alpine
WORKDIR /app
COPY --from=builder --chown=node:node /app/dist ./dist
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]Security Note
Never use chmod 777 to "fix" permission errors. While it makes the error disappear, it creates a security vulnerability by allowing any user to read, write, and execute your application files. 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