Don’t Use `sudo` with `npm` …still

This is a rehash of an article I wrote in 2015 about how you shouldn’t use the sudo command with npm. That is to say, you should never do sudo npm … anything. I feel that this needs a bit of a rehash since I’ve learned some new techniques for managing Node.js and global modules on my development machine. The advice here also applies universally, but the specific examples are tailored to local development on OS X. Most of the examples should work on any popular OS with minor modifications.

Development machines, production machines, or machines from any environment shouldn’t be using sudo npm or sudo yarn (and probably shouldn’t be using sudo at all, but that’s another story).

sudo npm install (and sudo npm $ANYTHING) is still a bad idea ™ for the at least following reasons:

  • npm install and others have the ability to run arbitrary scripts. Due to how npm is set up and the fact that you can alter the registry and it can use DNS, it is possible that you will accidentally install a malicious package in general, install a malicious package masquerading as a perfectly valid package, or install a package with good intentions that may run scripts that are somehow detrimental to your system if run as root.
  • Running sudo npm install (without -g) will create a local directory that can only be altered by the root user. This can really screw things up for you if you try to do npm <something> in the same directory or project later on.
  • Even sudo npm install -g with a valid installation target can mess things up for you and make it hard to use npm without sudo under some circumstances in the future -- particularly if you change your npmconfiguration midstream. The root user can and will create files in your npm cache and potentially a file like ~/.npm/_locks, and future npm install or npm install -g will give you the dreaded EACCES error.

This remains true even with new security measures added to npm. When it comes to using sudo with npm, Just don’t do it!

Avoid Global Installs

I’ve found it favorable to avoid global installations altogether in favor of project-level installations. This is made even more convenient with the addition of npx which will run a node module from the registry without permanently installing it to your system. So instead of running npm install -g react-native && react-native init you can do npx react-native init.

This will create a project that will have node_modules/.bin/react-native, so as long as you’re in said project you can use npx react-native or yarn react-native to actually run the locally-installed react-native command without having it installed globally. This also applies to tools like gulp and grunt and pretty much any other such tool you can think of.

Note: for whatever reason, Ionic and Cordova only seem to work properly as global installs for now.

If you were to run react-native init a lot, it would get annoying to have to install it with npx every single time you needed it. In this case, installing a global module makes sense. Let’s look at how to do that without sudo.

Set npm’s Prefix

npm has a configuration called prefix. This configuration setting tells npm where to install global modules (yarn also uses it). For more information on this configuration setting and others, see npm help 7 config.

You can set this per-install using npm --prefix=$PREFIX, but most likely you will want all of your global modules installed to the same place each time. You can update your global node configuration to do this. I did mine using npm config set prefix ~/.npm. This is equivalent to editing your ~/.npmrc to include the line prefix=~/.npm. You can make the prefix whatever you like, but it should be in a directory that your user has permissions to write to. That should be anything under your home directory, ~.

But wait! You also have to include modules installed here to your PATH. In your .zshrc.profile, or equivalent you should also set something like export PATH="$HOME/.npm/bin:$PATH". This will allow you to run globally installed modules that are located in ~/.npm/bin.

You can also configure yarn to use its own prefix if you like, but for me I have to swap between using yarn and npm for quite a few projects. It’s nice to have one configuration to worry about.

Using `n` to Manage Node.js Versions

My previous version of the article included instructions for using nvm. It’s a great tool, and you should use it if you wish, but I prefer TJ Holowaychuk’s n library for this purpose now.

Once you have installed a system-wide version of node, e.g. with brew install node, you should have access to npm and yarn. Now you can set your prefix and PATH. Once that’s done, you can run yarn global add n or equivalent. You should now have access to the n executable. which n should confirm this.

Now you’ll also have to update your N_PREFIX since n tries to install the versions it manages to /usr/local by default. I think that Homebrew suggests running sudo chown -R $(whoami) $(brew --prefix)/* or something like that, but I’m not a huge fan of doing this. I’d rather manage things in my home directory.

You can add export N_PREFIX=$HOME/.n or whatever you like. You must also add this to your path, for example: export PATH="$PATH:$N_PREFIX/bin". Now you can use n latest to install the latest version of node.js to a directory that you control.

Summary

To summarize, you can use the following steps to update your local OS X machine to allow you to use global Node.js modules and different versions of node without using sudo.

  1. brew install node, or apt-get install node or whatever you need to do to get node up on your machine. This should include executables npm and yarn.
  2. Set your prefix for global installs, e.g. npm config set prefix ~/.npm
  3. Update your PATH to include ~/.npm/bin. For example: echo 'export PATH="$HOME/.npm/bin:$PATH"' >> ~/.zshrc if you’re using zsh.

At this point you’re good to go for installing global modules. I think it’s good to avoid installing global modules except for commands that you will use frequently that won’t be installed to a corresponding project. For one-off commands such as initiating projects that you will only do every once in a while, you can use npx.

  1. Next, use yarn global add n which should create ~/.npm/bin/n.
  2. Do the equivalent of echo 'export N_PREFIX="$HOME/.n"' >> ~/.zshrc for your shell configuration

Now you can run n to install different versions of node to a directory that you control.

On build / production environments you should avoid global installs of modules as they are generally not needed. If they’re required, you can still do a similar setup with a user who only has permissions to install global node modules to a particular directory using --prefix and so on.

Original Article (includes more information on working with other kinds of environments and using nvm): https://medium.com/@ExplosionPills/dont-use-sudo-with-npm-5711d2726aa3