Dockerizing NodeJs application with multi-stage build

Sachith Siriwardana
5 min readJul 25, 2022

--

Photo by Lucas van Oort on Unsplash

Intro to Docker

Docker is one of the most popular open-source application containerization platform that is used for developing, shipping and running applications using containers. basically, deploying, testing, or scaling an application’s functionality independently is made possible by the modularization of its functionality using Docker containers.

To execute several containers on the same OS, Docker makes use of resource isolation in the OS kernel. On top of an abstraction layer of physical hardware resources, virtual machines (VMs) contain a whole OS with executable code.

If you are new to Docker please read this awesome article first, Docker — Beginner’s Guide.

Container Vs Virtual Machine

What is Docker Multi-Stage build? ( #or the concept)

The multi-stage build is a feature that was introduced in Docker 17.05, that allow us to build smaller final container image by specifying multiple helper imagers (stages) within the same Dockerfile . Each stage is used to complete a task. For example, building the project.

It allows us to provide multiple FROM instructions to a Dockerfile. Each of these FROM instructions can COPY artifacts from the previous build stage. So, it removes all the intermediate steps such as downloading the code, installing dependencies, testing, building...etc from the final image. which will reduce the additional layers from the final image and eventually the size of the final image will be smaller and also reduces the attack surface.

Here is a size comparison between a basic nodejs project with a typescript build process and eslint as linter.

normal build and multi-stage build size comparison

You can see that the image build using a multi-stage build is much smaller compared to the normal build. Because the normal build includes all of the development dependencies and typescript source code and also normal build include all the common Debian packages including build tools. While the multi-stage build doesn’t include any of that.

Before multi-stage build

Before the multistage build is available in docker, basically two dockerfiles were used to create a small image. First dockerfile for development that contains everything needed for development such as build tools and testing tools. Second dockerfile for production that only contains the application and only the required tools to run the application. Apart from these dockerfiles, an additional shell script is also needed to automate the process of building the final image. This method is referred to as the builder pattern.

A basic example

It's useful to know how this was done with the builder pattern before the multi-stage build. for this example, I am using a simple typescript nodejs project.

Dockerfile that is used to build the node application
Dockerfile that is used to create the final image
Bash script that is used to automate the process

To start you need to execute the bash script. When the bash script started to execute,

  1. It will build a new image from Dockerfile.build file. The Dockerfile will start from the node base image and install all the dependencies and dev dependencies. Then it will copy the application from local storage and build the code.
  2. Then it will create a new container from the previous image and copy the dist folder to local storage as distapp and remove the container.
  3. In the last step, it will build the final image from Dockerfile.prod file. The Dockerfile will start from the node alpine base image and install only the required dependencies. After that Dockerfile will copy distapp folder.
  • Also for the final image node:alpine image is used because the final image does not need to have common Debian packages such as building tools. because of that, this image is much smaller compared to the node image.
node and node:alpine image size comparison

Advantages of Multi-stage build

  1. In the builder pattern, we have to maintain separate dockerfiles for each stage, but in a multi-stage build, we only have one dockerfile in that one file we can have as many stages as we want.
  2. In the builder pattern, we have to maintain the bash script to automate the build process, but in a multi-stage build, we don’t need to maintain such a file.
  3. In the builder pattern, we have to copy the artificats to the local system, but in a multi-stage build, we don’t have to do that.
  4. In multi-stage build, we have the capability to build each stage individually using --target flag.

Let's Create a Multi-Stage build

Docker multi-stage build

First, there are some new syntax concepts for the multi-stage build,

  1. AS — This is used to provide an alias to the stage that starts with FROM (Ex : FROM node AS base) and this stage can be used as a base image for the next stages using the alias (Ex : FROM base).
  2. --from=stage — This is used as an option in COPY to copy files from the specified stage (Ex: COPY --from=base /usr/scr/app ./app).
  3. --target — This is used as an option in docker build to build each stage individually (Ex : docker build --target base -t project:base .). This option is useful when debugging.

let's create our multi-stage docker file for the nodejs application. For this example, I am also using a simple typescript nodejs project.

Dockerfile for multi-stage build

When started to build from Dockerfile,

  1. In the first stage, it will use node:17.9.0 as base then install the dependencies and dev dependencies and copy the application.
  2. In the second stage, it will use the previous base stage as a base image and execute the linting tools.
  3. In the third stage, it will use the previous linter stage as a base image and build the application.
  4. In the final stage, it will usenode alpine as the base image. Then install only the required dependencies and COPY build code from builder stage.

Compared to the builder pattern this multi-stage build reduces the complexity while producing the small final image as before.

multi-stage build and builder pattern image size comparison

I hope you’ve liked this article and I am very keen on hearing your thoughts about it. Just give a comment and I’ll be more than happy to reply.

ENJOY YOUR CODING! 🚀

--

--

Sachith Siriwardana
Sachith Siriwardana

Written by Sachith Siriwardana

Software Engineer at Sysco Labs | Computer Science Graduate | Interested in Software Engineering & Cloud Computing

Responses (2)