Install arbitrary node versions on Docker container via nvm
We’ve been using Docker to run our Node tests and encapsulate odd depencencies for a while now, and it’s worked out pretty well. We have a “baked-in” Node version via an official Node build, and add a couple of Docker layers on top for our needs. However, we’re in the middle of upgrading to Node 6, and we’re running into snags upgrading packages and extricating the mountain of Babel dependencies from our codebase (we’ll survive with ES6 for now). What this means is we’re flipping between Node versions and we need to have a way to run CI tests on Docker on an arbitrary Node version.
One way of solving this problem is having different Docker images for
the versions you’re targeting and configure the CI (Teamcity in this
case) to watch for specific git branches, then use the appropriate
Docker image. Another, hackier but more flexible solution is to select
and use a Node version at docker run
-time based on a
.nvmrc
file “But wait!”, I hear you say. Doesn’t that mean that
you’ll have an external dependency and you’ll install Node every time
you run your tests?! I would look off into the horizon, and say,
Sometimes we need to push the agenda forward. Sometimes we must do
non-kosher, hacky things to keep the builds going. Especially when
you have limited server control and build configs.
.
The purpose of this post is not to defend the choice of installing
Node every time we do docker run
(its indefensible) nor to
advocate that you should (you really shouldn’t), but rather to show a
way of running arbitrary Node versions on a Docker container, should you
choose to do so. It’s actually not complicated - though there is some
ENV
juggling necessary, due to the nature of nvm.
Here’s the heart of the matter:
# Substituting sh for bash here to use `source`. Beware - your build times could be
# longer if you are doing shell-intensive tasks!
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
# any necessary setup steps here...
# We're just setting it to the root user's home (root is default on docker)
# though you could set it to your' user's home if you need a non-root user
ENV NVM_DIR=/root/.nvm
# install nvm on the home dir above
RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
# make a folder for the app, why not
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# rely on project's .nvmrc file to install the appropriate version and run tests
CMD source ~/.nvm/nvm.sh && cd /usr/src/app && nvm install && npm i && npm test
Of interest are really these two parts:
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
#...
CMD source ~/.nvm/nvm.sh && cd /usr/src/app && nvm install && npm i && npm test
The first subs sh
for bash
, allowing us to
source
files for running subsequent commands. The second
activates nvm
and installs a given node version, followed
by any node commands needed.
This is not supposed to be a permanent or prod solution, but it gets the job done. Fun to do, but it’s going out as soon as we’re done with our upgrades!