Node version management in Docker containers

Sjuul vierkant

Sjuul Janssen – 11 April 2019
560 words in about 3 minutes

Recently I came across a solution to a Docker specific problem that I didn’t know about and that I think is not used very often. Mainly because you shouldn’t use it without knowing the implications. It can be a useful thing to know nonetheless.

I was building containers for a client where the codebase for historical reasons uses a few different node versions. Being more of a backend / devops developer myself I hadn’t really used nvm before.

Usually I would opt for using a Docker image FROM node:<version_here> but the version in use (perhaps for good reason) isn’t on Docker hub. And perhaps it’s actually more maintainable if the Dockerfile uses the .nvmrc file for determining which node version to use.

Building this container appeared to be harder than I expected. This is because nvm uses environment variables that are declared in the profile. Which is quite logical because its a developer tool and you wouldn’t want switching environments to have system-wide effects.

Current solutions

The reason why I’m writing this post is because most of the answers out there are giving advise that feels just wrong. Like this:

1
2
# replace shell with bash so we can source files
RUN rm /bin/sh && ln -s /bin/bash /bin/sh

Or hardcoding the node version in the Dockerfile:

1
ENV NODE_VERSION 4.4.7

If you’re using nvm instead of a Docker hub image it’s better to use the .nvmrc file in your folder as a single point where you document the used node version.

A better solution?

It was only after building a rvm container that I came across this Stack Overflow Answer

Don’t use RUN bash -l -c rvm install 2.3.3. It’s very scruffy. You can set shell command by SHELL [ “/bin/bash”, “-l”, “-c” ] and simply call RUN rvm install 2.3.3.

After this your Dockerfile gets a lot cleaner. But please note that you’re doing something unusual here. With -l you’re asking for a “login shell”. Which is not normally the case in a Docker container and it might have side-effects you didn’t intend to. But that mostly depends on what you’re doing. In most cases you’re safe. And in my case I don’t really mind because I was using multi stage builds in docker so the end result wasn’t affected by this. And in case you were using this in a development container I think you’re fine as well.

So this is the Dockerfile I came up with. Note that I don’t cleanup my apt file because it’s an intermediate build step

1
2
3
4
5
6
7
8
FROM debian

SHELL [ "/bin/bash", "-l", "-c" ]

RUN apt-get update && apt-get install -y curl
RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
# this now works
RUN nvm install && nvm use

Please do note that this doesn’t directly work in an Ubuntu container because the /root/.bashrc file in Ubuntu contains this:

1
2
# If not running interactively, don't do anything
[ -z "$PS1" ] && return

And it would then skip setting up the environment variables needed for nvm because in a non login shell $PS1 is not set.

Summary

I do think that this solution is cleaner on more than one front. But of course I’m open for suggestions ;) I hope this saves others time and helps clean up those otherwise bloated Dockerfiles.

At Kabisa, privacy is of the greatest importance. We think it is important that the data our visitors leave behind is handled with care. For example, you will not find tracking cookies from third parties such as Facebook, Hotjar or Hubspot on our website. Only cookies from Google and Vimeo are used in order to improve the user experience of our visitors. These cookies also ensure that relevant advertisements are displayed. Read more about the use of cookies in our privacy statement.