DEV Community

Cover image for Reduce the size of your NodeJS docker images by a multiple
Matthias
Matthias

Posted on

Reduce the size of your NodeJS docker images by a multiple

Cover image by Ian Taylor on Unsplash

Docker is a great tool to quickly generate and deploy containers for your NodeJS application to different platforms. But if you have checked the size of your final container image, you may have noticed that the image can get pretty heavy. Several hundred MB up to over one GB are not uncommon.

This memory size can lead to different problems. First of all, your docker images will consume a lot of disk space on your machine. The second problem is that downloading the image from a registry like docker hub can take quite some time, depending on your network connection. This problem gets even worse for uploading your image since your upload bandwidth is usually multiple times smaller than your upload bandwidth.
As you can see, there are many reasons to keep your docker images as small as possible. In this article, I will show you how to reduce the size of your docker images using different techniques.

Prerequisites

For this tutorial, you will need to have the following programs installed on your machine:

Getting started

We will use a basic AdonisJS app for this article and package it inside a docker image.
First of all, we create the AdonisJS app running the following command and answering the prompts. As for the project structure, we will select the web project structure.

npm init adonis-ts-app@latest hello-docker-world
Enter fullscreen mode Exit fullscreen mode

Next, we can go to the hello-docker-world directory and test our new app by running

node ace serve --watch
Enter fullscreen mode Exit fullscreen mode

After that, you can open localhost:3333 in your favorite browser and should see your new test application.

Now we can start packaging our application into a docker image. Therefore, we first create a new file called Dockerfile inside our project directory and paste the following snippet into the file.

FROM node:14
WORKDIR /myapp
COPY . .
RUN npm install && node ace build --production
CMD node build/server.js
Enter fullscreen mode Exit fullscreen mode

This will pull the node base image from the docker registry, change the working directory to /myapp, and copies the content of your local project directory to the image working directory. After that, it will run npm insall to install the project dependencies and build the app for production. When the container image gets executed, the CMD command will be executed inside the container, starting our webserver.

Now that we know what the Dockerfile does, it's time to build our image. Therefore we execute the following command inside our project directory.

docker build -t adonisjs:full .
Enter fullscreen mode Exit fullscreen mode

This will look up the instructions from the dockerfile in the project directory, build the image based on the instructions, and tag the resulting image as adonisjs:full.

After that, you can list your images by executing

docker image ls
Enter fullscreen mode Exit fullscreen mode

and you will see your newly created image with its size. In my case, it was 1.13 GB which is quite heavy.

REPOSITORY    TAG           IMAGE ID       CREATED         SIZE
adonisjs      full          8c35e9442e40   2 minutes ago   1.13GB
Enter fullscreen mode Exit fullscreen mode

This is because the node:14 is based on Debian stretch whit a lot of packages, resulting in the given image size.

One approach in reducing the size of our docker image is instead of using the node:14 stretch image using the node:14-alpine base image. This image is based on Alpine Linux, a minimal Linux and much smaller than other base images.

To use the new base image, we update our Dockerfile with the following snippet.

FROM node:14-alpine
WORKDIR /myapp
COPY . .
RUN npm install && node ace build --production
CMD node build/server.js
Enter fullscreen mode Exit fullscreen mode

After that, we run

docker build -t adonisjs:alpine .
Enter fullscreen mode Exit fullscreen mode

which will again build an image from our project but this time based on the Alpine base image and tag it adonisjs:alpine.

When you now list your docker images using docker image ls, you will see your new image, which is much smaller than the first image. In my case, the new image has only 303MB, which is less than a third of the previous image.

REPOSITORY    TAG           IMAGE ID       CREATED         SIZE
adonisjs      full          8c35e9442e40   2 minutes ago   1.13GB
adonisjs      alpine        43e7a8874973   4 minutes ago   303MB
Enter fullscreen mode Exit fullscreen mode

But we can get the image even smaller and save more space with the following approach.

Multi-stage builds

At the moment, the entire content of your project folder is copied inside the image, and the project dependencies are installed. After that, your image contains all project related files, but not all of them are needed for production. For example, you won't need your source files, and also your dev-dependencies don't have to be included in your production image.

To solve this problem, you can use multi-stage builds. First, we will again update our Dockerfile using the following snippet.

FROM node:14-alpine AS build
WORKDIR /myapp
COPY . .
RUN npm install && node ace build --production

FROM node:14-alpine
COPY --from=build /myapp/build /build
CMD node build/server.js
Enter fullscreen mode Exit fullscreen mode

Again, this will use the node:14-alpine image as base image but name it as build. As in the previous steps, we will copy and build our application but won't finish after these steps. As you can see, after building the application, we use a fresh base image and copy only the generated build directory from our previous build image to the new image, which will become our new production image.

Using this approach, we can have a full build environment with all necessary dependencies. Still, when it comes to shipping the production image, we can use a minimal base image and only the generated files we need for executing our application.

Now you can again build and tag your image using the following command

docker build -t adonisjs:multistage .
Enter fullscreen mode Exit fullscreen mode

and list all your images by executing docker image ls.
You can see, the size of the resulting image decreased again, in my case, by almost a third to 117MB.

REPOSITORY    TAG           IMAGE ID       CREATED         SIZE
adonisjs      full          8c35e9442e40   2 minutes ago   1.13GB
adonisjs      alpine        43e7a8874973   4 minutes ago   303MB
adonisjs      multistage    6f91568fbe8f   6 minutes ago   117MB
Enter fullscreen mode Exit fullscreen mode

As you can see, multi-stage builds helped us to decrease the size of our final docker image by almost ten times, which will save a lot of resources and time while storing or up-/ downloading the image.

If you want to read more about multistage-builds, see the official docker documentation here

I hope you enjoyed the article and happy coding 🧑‍💻

Top comments (1)

Collapse
 
buffolander profile image
Bruno Soares

Wanna try getting it down to 51MB?