react-docker-optimization/README.md
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

181 lines
No EOL
6.2 KiB
Markdown

Pour la version en français, c'est ici: [Français](#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:
```Dockerfile
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:
```shell
npm install
```
Then run the following command to build the docker image:
```shell
docker build -t 'react-simple' .
```
You can inspect your images and verify the size yourself with the following command:
```shell
docker image ls
```
I use Nushell and rigrep to filter only on the image in question using the following command:
```shell
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](./assets/react-simple-size.png)
### 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:
```Dockerfile
# 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:
```shell
docker build -t 'react-multistage' .
```
You can run the following command to verify that the new image size is smaller than the previous one:
```shell
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](./assets/react-multistage-size.png)
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 :
```Dockerfile
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:
```shell
npm install
```
Puis en lançant la commande suivante pour builder l'image:
```shell
docker build -t 'react-simple' .
```
Vous pouvez inspecter la taille de votre image avec la commande suivante:
```shell
docker image ls
```
J'utilise personnellement Nushell et rigrep pour ne filtrer que sur l'image en question:
```shell
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](./assets/react-simple-size.png)
### 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:
```Dockerfile
# 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:
```shell
docker build -t 'react-multistage' .
```
Vous pouvez relancer la comande suivante pour vérifier que la taille a bien diminué cette fois-ci:
```shell
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](./assets/react-multistage-size.png)
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% !