Reduce the size of your NodeJS docker images by a multiple
Tags: webdev, beginners, docker
Published: 2021-08-03 17:42:10

---
title: Reduce the size of your NodeJS docker images by a multiple
published: true
description: How to reduce the size of your NodeJS docker images by multiple using multi-stage builds
tags: webdev,beginners,docker
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u55m8l4yoinutopkgopj.jpg
---

Cover image by [Ian Taylor](https://unsplash.com/@carrier_lost?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/docker?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)

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:
- [NodeJS](https://nodejs.org/en/)
- [Docker](https://www.docker.com)

## Getting started 
We will use a basic [AdonisJS](https://adonisjs.com) 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.
```bash
npm init adonis-ts-app@latest hello-docker-world
``` 

Next, we can go to the `hello-docker-world` directory and test our new app by running 
```bash
node ace serve --watch
```
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. 

```Dockerfile
FROM node:14
WORKDIR /myapp
COPY . .
RUN npm install && node ace build --production
CMD node build/server.js
```

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. 
```bash
docker build -t adonisjs:full .
```
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 
```bash
docker image ls
```
and you will see your newly created image with its size. In my case, it was 1.13 GB which is quite heavy.

```bash
REPOSITORY    TAG           IMAGE ID       CREATED         SIZE
adonisjs      full          8c35e9442e40   2 minutes ago   1.13GB
```
 
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.

```Dockerfile
FROM node:14-alpine
WORKDIR /myapp
COPY . .
RUN npm install && node ace build --production
CMD node build/server.js
```

After that, we run 
```bash
docker build -t adonisjs:alpine .
```
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. 
```bash
REPOSITORY    TAG           IMAGE ID       CREATED         SIZE
adonisjs      full          8c35e9442e40   2 minutes ago   1.13GB
adonisjs      alpine        43e7a8874973   4 minutes ago   303MB
```
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. 

```Dockerfile
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
```

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
```bash
docker build -t adonisjs:multistage .
```
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.

```bash
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
```

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](https://docs.docker.com/develop/develop-images/multistage-build/)

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