Using Multiple Node Versions Without nvm
Managing projects on different versions of node can be a bit annoying. Some Linux distributions ship ancient node versions that have never even heard of big arrow functions.
Table of Contents
Node Version Manager (nvm)
Some years ago I tried the “Node Version Manager” nvm
— a shell
script that downloads and installs node versions on demand. To
download and use a specific version of node, a single command is
sufficient:
nvm install 10.10.0
It automatically downloads the selected version and symlinks it on
your system. This allows easy switching of node versions when moving
from one to another project. To remember which version is needed for a
specific project, .nvmrc
files can be used to declare a project
specific version.
As an avid user of docker, there is no node1 project that I run
or develop without docker. When developing, there have been conflicts
with node dependencies that were installed by the host system’s npm
,
which can be incompatible with the node version in the docker
container. As the docker development container bind-mounts the
node_modules
directory in order to save precious development time
and bandwidth, those different versions can interfere and lead to
conflicts.
Even when using the same version of node on your host system and in
the docker container, version conflicts for binary compiled
dependencies can still arise. node-sass
for example is compiled on
installation and was unable to run in the docker container.
The prettier alternative
As I have been using docker for my development workflow anyway, I bound some project-specific aliases to use the containerized version of node instead of the version of node present on the host system. This is easy to do and allows to run projects with different versions of node concurrently. To set the aliases I use a simple bash script that I source when I start development.
#!/bin/bash
set -euo pipefail
# -e abort on error
# -u unset variables are errors
# -o pipefail pipe does not adopt exit status of last command
# The docker compose project name. The containers will be named
# "my-super-important-project_<service>".
export COMPOSE_PROJECT_NAME=my-super-important-project
# The node version to use. Also export it to make it available for
# other tools like docker-compose in order to choose the right docker
# base image
export NODE_VERSION="12.4.0"
# The tell docker to start the node process with the used and group id
# from the host system. This prevents files being created in bind
# mounts that are not deleteable by the unprivileged user.
DOCKER_USER="$(id -u ${USER}):$(id -g ${USER})"
# The base 'docker run' command with a couple of parameters:
# --init spawn an init process responsible for handling signals
# -i interactive -> accept input from stdin
# -t allocate a pseudo tty
# --rm remove the container after it exits
# -u… set the group and user id of the docker main process
# -v… mount the current host working directory into the container
# -w… set the container working directory to the host working directory
DOCKER_COMMAND="docker run --init -it --rm -u $DOCKER_USER -v $(pwd):$(pwd) -w $(pwd)"
alias npm="$DOCKER_COMMAND node:$NODE_VERSION npm"
alias node="$DOCKER_COMMAND node:$NODE_VERSION node"
alias npx="$DOCKER_COMMAND node:$NODE_VERSION npx"
Each project has its own source.sh
file. Build scripts and Makefiles
can use the environment variables and aliases defined in this
file. This allows for a centralized definition of project variables
and versions that is sufficient for my use cases.
TODO Linux signal handling
You might not have used the --init
flag in your docker run
commands yet. This is mainly relevant if you use docker with an
interactive tty. When using the flag, docker spawns an additional
process (Tini) responsible for handling various signals. More about
Linux process signal handling in an upcomping post.
TL;DR
- Download the source.sh script into your project directory
- Change the
COMPOSE_PROJECT_NAME
variable - Run
. source.sh
orsource source.sh
- Execute your favorite docker commands (
npm i
)
-
or any other project (?) ↩︎