Common causes
- Global installs (npm install -g) into /usr/local/lib/node_modules, which belongs to root when Node came from a system package or installer
- An earlier sudo npm command left root-owned files in ~/.npm
- node_modules or package-lock.json in the project was created by sudo npm install or inside a Docker container as root
- The project folder is on a mount or share where your user lacks write permission
- Running npm in CI or Docker as a non-root user against files copied in as root
How to fix it
- Fix a root-owned cache. If the path is under ~/.npm, run sudo chown -R $(id -u):$(id -g) ~/.npm. This is the fix npm itself suggests for 'Your cache folder contains root-owned files'.
- Fix the project folder. If the path is inside your project, run sudo chown -R $(id -u):$(id -g) node_modules package-lock.json, or delete node_modules and reinstall with plain npm install.
- Install Node with a version manager. Install nvm (or fnm/Volta) and run nvm install --lts. Node and its global packages then live in your home folder, so npm install -g never needs sudo.
- Or set a user-owned global prefix. Run mkdir -p ~/.npm-global and npm config set prefix ~/.npm-global, then add export PATH=~/.npm-global/bin:$PATH to ~/.bashrc or ~/.zshrc and open a new shell.
- Use the right user in Docker. Set USER node before npm install in the Dockerfile and copy files with COPY --chown=node:node . . so the container user owns the app folder.
Terminal
# root-owned cache
sudo chown -R $(id -u):$(id -g) ~/.npm
# user-owned global folder (alternative to nvm)
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc How to stop it happening again
- Never run sudo npm install, globally or in a project
- Manage Node with nvm, fnm or Volta on developer machines
- Use npx for one-off CLIs instead of installing them globally
- Run containers and CI jobs as the same user that owns the files