Directory to showcase how one can optimize docker image sizes with a simple React application
Find a file
hadestructhor 0d52d2c225
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
ci/woodpecker/manual/workflow Pipeline was successful
feat: Dockerfile multistage
2025-01-06 20:08:32 +01:00
.woodpecker feat: Dockerfile multistage 2025-01-06 20:08:32 +01:00
assets feat: Dockerfile multistage 2025-01-06 20:08:32 +01:00
public feat: Simple Dockerfile version 2025-01-04 14:29:35 +01:00
src feat: Simple Dockerfile version 2025-01-04 14:29:35 +01:00
.dockerignore feat: Dockerfile multistage 2025-01-06 20:08:32 +01:00
.gitignore feat: Simple Dockerfile version 2025-01-04 14:29:35 +01:00
Dockerfile feat: Dockerfile multistage 2025-01-06 20:08:32 +01:00
package-lock.json feat: Dockerfile multistage 2025-01-06 20:08:32 +01:00
package.json feat: Dockerfile multistage 2025-01-06 20:08:32 +01:00
README.md feat: Dockerfile multistage 2025-01-06 20:08:32 +01:00

Pour la version en français, c'est ici: Français

English

How to reduce the size of your docker 🐳 images 💿?

We're starting from a basic React application générated with a simple Dockerfile with disastrous conséquences:

FROM node:18
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]

For your information, the image weight a whopping 2.03GB 🤯 !!!!! A mountain 🏔️ of data for a simple web application that is empty at the moment!!!

You can test it yourself by first running the following command:

npm install

Then run the following command to build the docker image:

docker build -t 'react-simple' .

You can inspect your images and verify the size yourself with the following command:

docker image ls

I use Nushell and rigrep to filter only on the image in question using the following command:

docker image ls | from ssv | where repository starts-with 'react'

Here's the result of running this command that shows how shamefully big the image is: Size of the image

First optimisation: using a .dockerignore file and multi-stage build

The first optimization that we can do is using a .dockerignore file. Just like a .gitignore file, a .dockerignore file allows us to exclude file and folders that we don't want to include in the docker image that we are building.

Here's the .dockerignore file that I added to this simple project to mainly exclude the dark hole that node_modules are node_modules:

node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
build
assets

In our context, this command only optimizes the build time and not the build size, as the 309 MB that the node_modules folder occupies are not needed to build the docker imagen but as we run the npm install command in any case inside the Dockerfile, this folder is generated and included from inside the Dockerfile.

A better optimisation of the image is to use multi-stage build. Multi-stage builds allows us to include only what is necessary to run the image, and to not include the build files or node_modules for example.

Here's the new version of the Dockerfile using multi-stage build:

# Build stage of the application
FROM node:18 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Production stage to run the application
FROM node:18 AS production
COPY --from=build /app/build /prod
RUN npm install --global serve
EXPOSE 3000
CMD ["serve", "prod/"]

You can run the following command to build the image:

docker build -t 'react-multistage' .

You can run the following command to verify that the new image size is smaller than the previous one:

docker image ls

I am still usig Nushell on my end to have a pretty print and filter on the images that I want which gives me the following output: Multistage image size

The image now weighs 44 MB less, which is 1.27 times smaller, and a whopping 21.67% reduction in size!

Français

Comment réduire la taille de vos images 💿 docker 🐳 ?

On part d'une application React tout juste généré et avec un Dockerfile simple mais avec de grosses conséquences :

FROM node:18
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]

Pour indication, l'image qui en résulte pèse environ 2.03GB 🤯 !!!!! Une montagne 🏔️ de données pour une simple application web vide pour l'instant !!!

Vous pouvez le testez vous même en lançant d'abord la commande suivante:

npm install

Puis en lançant la commande suivante pour builder l'image:

docker build -t 'react-simple' .

Vous pouvez inspecter la taille de votre image avec la commande suivante:

docker image ls

J'utilise personnellement Nushell et rigrep pour ne filtrer que sur l'image en question:

docker image ls | from ssv | where repository starts-with 'react'

Voici un aperçu de ce que cette commande m'affiche avec le poids de l'image assez désastreux: Taille de cette image

Première optimisation: utilisation de .dockerignore et multi-stage

Une des première optimisation que l'on peut faire est d'utiliser un fichier .dockerignore. Tout comme un fichier .gitignore, le fichier .dockerignore permet de ne pas include certains dossiers ou fichier qui ne sont pas nécessaire pour l'image que l'on construit.

Voici le .dockerignore que j'ai rajouté à ce simple projet pour notamment éviter d'inclure le trou noir qu'est le dossier node_modules:

node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
build
assets

Cependant, ce que cette commande optimise est le temps du build de l'image, car les 309 MB qu'occupe actuellement ce dossier ne sont pas nécessaire à inclure dans l'image lors du build de celle-ci, mais comme nous lançons la commande npm install dans tous les cas dans le Dockerfile, ils sont réinstallé et ré-inclut dans l'image.

Une meilleure optimisation de l'image est d'utiliser le multi-stage build. Le multi-stage build permet de n'inclure que ce qui est nécessaire pour exécuter votre programme et de ne pas inclure les fichier de build ou les node_modules par exemple.

Voici la nouvelle version du Dockerfile découpé en multi-stage:

# Build stage of the application
FROM node:18 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Production stage to run the application
FROM node:18 AS production
COPY --from=build /app/build /prod
RUN npm install --global serve
EXPOSE 3000
CMD ["serve", "prod/"]

Vous pouvez lancer la commande suivante pour construire l'image:

docker build -t 'react-multistage' .

Vous pouvez relancer la comande suivante pour vérifier que la taille a bien diminué cette fois-ci:

docker image ls

Pour ma part j'utilise toujours Nushell pour avoir cette belle présentation des résultats filtré: Taille de l'image multistage

L'image pèse maintenant 44 MB de moins, on a une image 1.27 fois plus petite, soit une diminution de la taille de 21.67% !