How to install a specific Node.js version in an alpine Docker image?

Csaba Apagyi
Geek Culture
Published in
3 min readDec 7, 2021
Why is this so complicated?

As asked by these people: 1, 2, 3, 4 and more.

Unless you’re starting out with a Node image, you probably ran into this surprisingly complicated problem.

Turns out, the alpine package repo only stores a single version for every package. So if you’re pinning a strict Node version (as you should), chances are, you won’t find it in there. What’s worse, if you do find it and start developing with this setup, they will remove the package as soon as there’s a new version available. So you’re forced to update or look for a better solution. See also: The problem with Docker and Alpine’s package pinning

According to the Alpine team:

The official recommendation is to keep your own mirror / repository with all the specific package and their versions that you may want to use.

I guess that works, but assuming you’re not crazy enough to do that, here are some other options.

1, Use a binary

This isn’t as easy as it sounds. Simply using the official Node binary of your platform will fail and you either have to install additional dependencies or build the binary yourself (you can find built binaries though).

This is a popular solution but you lose cross-platform support. With the spread of ARM platforms (e.g. Apple silicon) this already sub-optimal solution should be avoided.

2, Use a Node version manager, such as NVM

Installing more and more tools defeats the purpose of using an Alpine image, but in any case, nvm just didn’t work for me on the latest Alpine.

3, Copy binaries from the official Node image

This works surprisingly well. It’s cross-platform and even this naive implementation below does not copy too much or overwrite anything that would cause an issue.

FROM node:$NODE_VERSION-alpine AS nodeFROM baseCOPY --from=node /usr/lib /usr/lib
COPY --from=node /usr/local/share /usr/local/share
COPY --from=node /usr/local/lib /usr/local/lib
COPY --from=node /usr/local/include /usr/local/include
COPY --from=node /usr/local/bin /usr/local/bin

You can see it practice here, I’m using it to enable the last option:

4, Find an image with pre-installed Node

So your base image doesn’t have Node because you work with other frameworks, such as Ruby, Python, etc. But it might be possible to find a base Alpine image with Node installed as well.

For Ruby, docker-ruby-node does that, however, you can’t pin exact Node versions and they also overwrite images when there’s a Node update, leaving you with the same problem as the Alpine package manager.

For this reason, I created ruby-node-alpine: a project that enables building Ruby + Node Alpine images for custom, specific versions.

You can find these images on DockerHub too:

docker run thisismydesign/ruby-node-alpine:3.0.2-16.13.0-alpine /bin/sh -c "ruby --version && node --version"

I‘m pretty sure this banal issue wasted thousands of hours of development time. Finally, I will never have to care about it again! 🎉

--

--