Notes

Installing Node inside Docker Ubuntu and installing npm packages globally as root

Edit on GitHub

Docker
4 minutes
 1FROM ubuntu:21.04
 2
 3LABEL description="Install Node with nvm"
 4
 5# Install Node with nvm
 6ARG NODE_VERSION=14.17.0
 7ENV NPM_CONFIG_USER=root
 8ENV NVM_DIR=/root/.nvm
 9ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
10
11RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash  \
12    && . ${NVM_DIR}/nvm.sh \
13    && . ${NVM_DIR}/bash_completion \
14    && echo "\n npm version is: $(npm -v)" \
15    && echo "\n Node version is: $(node -v)"
16
17# Install npm packages and tooling
18# RUN npm i -g --unsafe-perm expo-cli turtle-cli
19# RUN npm i -g netlify-cli appcenter-cli firebase-tools
20
21CMD bash

Installing packages globally

If you are installing packages globally as the root user, you get the EACCES: permission denied error. Which is why it is recommended that you install node with a version manager, like nvm. (Or manually change the default directory for npm)

Running Node as root user

While installing with nvm is commonly accepted as the solution to sudo and root problems, it still didn’t work for me when installing certain packages globally (e.g. expo-cli, turtle-cli). For packages like these, it is usually a postinstall script that check/fix permission since npm runs postinstall scripts as user nobody for safety (and not root).

(nobody is the default user for npm while the default user for a docker container is root). In situations like these, where you want to install packages as root, you can install a particular package with the --unsafe-perm flag.

When installing packages globally as root, you’ll get a bunch of sh: 1: node: Permission denied errors and will have to use flags like --unsafe-perm=true and --allow-root to get rid of them

npm config set user 0
npm config set unsafe-perm true
  • unsafe-perm - Set to true to suppress the UID/GID switching when running package scripts. If set explicitly to false, then installing as a non-root user will fail. Default is false if running as root, and true otherwise
  • --unsafe-perm is always true unless using root
  • user - The UID to set to when running package scripts as root. default is nobody
  • 0 is always the UID of root user

You can pass these config values as flag by adding a -- to the beginning. If you don’t pass it a value, it becomes true. --unsafe-perm is the same as --unsafe-perm true is the same as --unsafe-perm=true

You can do it on a per install command basis

1npm i -g --unsafe-perm --allow-root expo-cli

Or you can set it for the entire script/container (which is not recommended)

1npm config set user 0
2npm config set unsafe-perm true
3npm i -g expo-cli

While most online answers will include the --allow-root flag (or npm config set user 0), i went the ENV NPM_CONFIG_USER=root route where i set the npm user for the entire Dockerfile

1ENV NPM_CONFIG_USER=root

In the end, i don’t quite see the benefit of installing via nvm since i ended up using these flags anyway and installing packages globally was a troubling experience.

Adding Node to $PATH

If after installing node with nvm you got /bin/sh: 1: node: not found on the next step, you need to add node to $PATH so that it knows where to run node from

1ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"

Issues with sourcing ~/.bashrc

source ~/.bashrc did not work as expected inside the docker container. Something to do with Docker using /bin/sh instead of /bin/bash. You can change the shell inside the Dockerfile with SHELL ["/bin/bash", "-c"] or run a particular command in a different shell with RUN ["/bin/bash", "-c", "echo hello all in one string"], but neither worked for me when sourcing ~/.bashrc after having installed nvm. In the end i looked at the manual installation steps to see what nvm was adding to bashrc (setting NVM_DIR and then calling a script inside NVM_DIR called nvm.sh) and referenced that particular bit directly inside the command (instead of adding it to bashrc). I’m cool with it since i don’t expect to be using nvm lots of time inside the container. It’s pretty much a one-time thing to install a particular node version. The only reason i installed with nvm was to get rid of root user woes.

Related