Skip to content
Nilay Kabariya

Entry No.086·Fixes··2 min read

npm error code EACCES: permission denied, 3 causes tested

npm error code EACCES with syscall mkdir or open: a global install, a cache that sudo left behind, or Docker. Each reproduced, with the fix that lasts.

by Nilay#npm#node#dockerFIXED

The error, verbatim

Jump to the fix ↓
npm error code EACCES
npm error syscall mkdir
npm error path /opt/rootnode/lib/node_modules/cowsay
npm error errno -13
npm error Error: EACCES: permission denied, mkdir '/opt/rootnode/lib/node_modules/cowsay'
✓ Reproduced on Ubuntu 24.04 (GitHub Actions runner), Docker

Tested on

Node / npm
24.15.0 / 11.x; node:24-slim image
OS
Ubuntu 24.04 (GitHub Actions runner), Docker
Contents
  1. Global installs: npm install -g01
  2. Your cache folder contains root-owned files02
  3. Docker: EACCES mkdir ‘/app/node_modules’03
  4. How this was tested04

EACCES means your user isn’t allowed to write where npm is trying to write. The path line says where that is, and in my tests there were three places it came from:

path in the error syscall What happened
/usr/.../lib/node_modules/<package> (Node’s own folder) mkdir npm install -g as a normal user
~/.npm/_cacache/... open sudo npm ran earlier and left root-owned files in your cache
/app/node_modules mkdir Docker: USER node, but the folder belongs to root

The fix is different for each, so match your path first.

Global installs: npm install -g

I put Node in a folder owned by another user, the way a system install leaves it under root, and ran npm install -g cowsay as a normal user:

npm error code EACCES
npm error syscall mkdir
npm error path /opt/rootnode/lib/node_modules/cowsay
npm error Error: EACCES: permission denied, mkdir '/opt/rootnode/lib/node_modules/cowsay'

npm config get prefix showed why: global packages go next to Node itself, and that folder isn’t yours.

Give npm a global folder in your home directory, and put its bin on your PATH:

npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Use ~/.zshrc instead of ~/.bashrc if your shell is zsh. A Node version manager such as nvm does the same thing for you: it installs Node inside your home folder, so global installs never need root.

Your cache folder contains root-owned files

After one sudo npm install, the next normal npm install in any project failed, this time on the cache:

npm error code EACCES
npm error syscall open
npm error path /home/dev/.npm/_cacache/index-v5/98/20/8cf39748e26add...
npm error Your cache folder contains root-owned files, due to a bug in previous versions of npm which has since been addressed.

Despite the wording, this happened on a current npm. Running npm as root was enough.

The error’s last line prints the fix: give your cache back to your user.

sudo chown -R $(id -u):$(id -g) ~/.npm

Then stop using sudo with npm. The global-folder fix above is what makes that possible.

Docker: EACCES mkdir ‘/app/node_modules’

The official Node images have a node user, and switching to it is good practice. But WORKDIR /app creates the folder as root, so npm install after USER node can’t write in it:

npm error code EACCES
npm error syscall mkdir
npm error path /app/node_modules
npm error Error: EACCES: permission denied, mkdir '/app/node_modules'

Hand the folder to node before switching users, and copy files in with the same owner:

FROM node:24-slim
WORKDIR /app
RUN chown node:node /app
USER node
COPY --chown=node:node package.json .
RUN npm install

How this was tested

Ubuntu 24.04 on a GitHub Actions runner, October 22, 2026. A new user without root ran npm against a Node 24.15.0 install in a folder it couldn’t write to, before and after setting a user prefix. The same user’s cache was then touched once by sudo npm install, and the next install was run as the user. The Docker case built a node:24-slim image with USER node, with and without changing the owner of /app. I didn’t test macOS. Every message above is the real output, from the test script.

— N.K., end of entry No.086

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.081

    Docker permission denied on docker.sock after usermod (Ubuntu)

    Added yourself to the docker group and still get permission denied? Your shell keeps its old groups until a new login. Reproduced on Ubuntu 24.04.

    > permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.48/containers/json": dial unix /var/run/docker.sock: connect: permission denied

    DEPLOY3 min
  2. No.057

    Node 26 vs Node 24: what breaks when you upgrade, tested

    Most projects move over cleanly: jsonwebtoken, bcrypt, sharp, better-sqlite3 13, tsx and Vitest all worked unchanged. What broke: Corepack and Yarn are gone (Docker builds fail with exit code 127), native modules installed under Node 24 refuse to load, fs.rmdir with recursive now throws (prisma generate with zod-prisma-types fails on the second run), and fs.W_OK disappeared without an error.

    TESTED8 minWindows
  3. No.093

    Fix: bigint Failed to load bindings, pure JS will be used

    bigint: Failed to load bindings, pure JS will be used. Why npm 12 shows it on every install, whether it matters, and how to get the native build back.

    > bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)

    FIXED3 minWindows
  4. No.091

    Fix: npm error code EALLOWREMOTE (and EALLOWGIT), tested

    npm error code EALLOWREMOTE: Fetching packages of type remote have been disabled. Every case reproduced on npm 12, from npx to npm ci, with each fix.

    > npm error code EALLOWREMOTE

    FIXED4 min

Post card · Every Sunday

What broke this week.

One email on Sundays: the Node, TypeScript, Python and AI SDK releases that broke builds that week, each one reproduced, with a link to the fix that worked. Never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text