Docker Multistage Images: Add Node Support to a Non-Node App Container
You should’ve been in a situation where you need nodejs and yarn in your package for building the front end, but you don’t want to go through all steps for installing it in the container. Or, in the case where you’re using a bit older Apline that fits your runtime version and that Alpine doesn’t support the latest Node LTS and the upgrade of the backend requires so more resources.
Solution 1: Build Node From Source
Because the official node binaries are built with glibc
, while Alpine supports musl
; installing glibc is not a reliable option, and it requires effort.
You can see a code example of how to build it from the official node docker image source.
Solution 2: Use Docker Multistage Image
This is the subject of this article, which is to use the multistage image feature in docker. I googled how to build the Node on Alpine, then I found an interesting repository: https://github.com/jsntv200/docker-ruby-node that uses the multistage image to achieve a very similar goal—credits to @jsntv200!.
This works like charm by including it and then copying the node and yarn files to the building image in the step after as follows:
# ... backend runtime work here
# This is the node image we want
FROM node:16.14.2-alpine AS nodejs
# For example, using a fullstack framework like Laravel or Rails.
FROM backend AS webpack
COPY --from=nodejs /usr/local/bin/node /usr/local/bin/
COPY --from=nodejs /usr/local/bin/yarn /usr/local/bin/
COPY --from=nodejs /usr/local/lib/node_modules /usr/local/lib/node_modules
# In case yarn installed without npm
COPY --from=nodejs /opt/ /opt/
# Do the linking
RUN ln -sf /usr/local/bin/node /usr/local/bin/nodejs \
&& ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \
&& ln -sf /opt/yarn*/bin/yarn /usr/local/bin/yarn \
&& ln -sf /opt/yarn*/bin/yarnpkg /usr/local/bin/yarnpkg
# Do the real build things here.
Keep in mind that the node binaries should work with the OS image of the backend runtime. In this case, both are Apline, and the versions were close enough that they don’t have any breaking changes. Some cases might need to install a few extra packages.
Conclusion
After a few hours of trials and errors with different solutions, I overlooked the power of the multistage image feature. In fact, I didn’t know how to add files from one to another. However, when I learned that it was possible, it was a one-liner solution!