From ec5b3de9cf5b8372edd4b9e9018a283e33a874b8 Mon Sep 17 00:00:00 2001 From: hadestructhor <60148800+hadestructhor@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:06:17 +0100 Subject: [PATCH] feat: nginx-alpine Dockerfile --- .woodpecker/.workflow.yml | 4 +- Dockerfile | 2 +- README.md | 88 ++++++++++++++++++++++++++++- assets/react-nginx-alpine-size.png | Bin 0 -> 22138 bytes 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 assets/react-nginx-alpine-size.png diff --git a/.woodpecker/.workflow.yml b/.woodpecker/.workflow.yml index 585a7b6..1203c7d 100644 --- a/.woodpecker/.workflow.yml +++ b/.woodpecker/.workflow.yml @@ -21,5 +21,5 @@ steps: - /var/run/docker.sock:/var/run/docker.sock commands: - docker login forgejo.transprot.eu -u $DOCKER_USERNAME -p $DOCKER_PASSWORD - - docker build -t 'forgejo.transprot.eu/public/react-nginx:latest' . - - docker push forgejo.transprot.eu/public/react-nginx:latest \ No newline at end of file + - docker build -t 'forgejo.transprot.eu/public/react-nginx-alpine:latest' . + - docker push forgejo.transprot.eu/public/react-nginx-alpine:latest \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f11e443..127bc84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY . . RUN npm run build # Production stage to run the application -FROM nginx AS production +FROM nginx:stable-alpine AS production COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/README.md b/README.md index ecd9a37..367216c 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,49 @@ But there's a tiny problem with this solution, this only optimizes the perfomanc Here's an image showing the size of all the images so far: ![Nginx image size](./assets/react-nginx-size.png) +### Fourth optimization: The obvious, Nginx Alpine + +The size of the regular Nginx server maybe be bigger than the Node Alpine version, but that's not really a fair comparison. + +So to keep this fair, we'll also use the Nginx Alpine version to build the application. + +Here's the updated Dockerfile to do so: +```Dockerfile +# Build stage of the application +FROM node:18-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# Production stage to run the application +FROM nginx:stable-alpine AS production +COPY --from=build /app/build /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +You can run the following build command to generate the image: +```shell +docker build -t 'react-nginx-alpine' . +``` + +To run this image, you will need to run the following command: +```shell +docker run -d -p 80:80 --name react react-nginx-alpine +``` + +And you can also run the following command to check the size of the image: +```shell +docker image ls +``` + +Here's an image showing the size of all the images so far: +![Nginx alpine image size](./assets/react-nginx-alpine-size.png) + +The image now weighs 1 954,5 MB less, which is 26,88 times smaller, and a whopping 96,28% reduction in size!!!!! And with better perfomances of the server to top it off!!! + # Français @@ -381,4 +424,47 @@ docker image ls Mais il y a un petit hic avec cette solution, l'image est plus performante mais plus volumineuse, étant donnée que l'image classique de Nginx est plus volumineuse que celle de Node Alpine. Voici une image de la taille de nos images docker jusqu'à présent : -![Nginx image size](./assets/react-nginx-size.png) \ No newline at end of file +![Nginx image size](./assets/react-nginx-size.png) + +### Quatrième optimisation: l'évident, Nginx Alpine + +La taille de l'image classique de Nginx est plus grosse que celle de Node Alpine, mais ce n'est pas vraiment une comparaison équitable. + +Pour rendre cette situation plus équitable, nous allons utiliser la version Alpine de Nginx. + +Voici à quoi ressemble notre Dockerfile à présent: +```Dockerfile +# Build stage of the application +FROM node:18-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# Production stage to run the application +FROM nginx:stable-alpine AS production +COPY --from=build /app/build /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +Vous pouvez lancer la commande suivante pour construire l'image: +```shell +docker build -t 'react-nginx-alpine' . +``` + +Pour démarrer un conteneur, vous pouvez lancer la commande suivante: +```shell +docker run -d -p 80:80 --name react react-nginx-alpine +``` + +Vous pouvez également vérifier la taille de l'image en lançant cette commande: +```shell +docker image ls +``` + +Voici une image de la taille de nos images docker jusqu'à présent: +![Nginx alpine image size](./assets/react-nginx-alpine-size.png) + +L'image pèse 1 954,5 MB de moins que celle de départ, ce qui est 26,88 fois plus petit, et une énorme reduction 96,28% de la taille de l'image !!!!! Et pour couronner le tout, avec de meilleur performances !!! \ No newline at end of file diff --git a/assets/react-nginx-alpine-size.png b/assets/react-nginx-alpine-size.png new file mode 100644 index 0000000000000000000000000000000000000000..cdce6822e21b50eb6873f66e18868b67ccd2e8c3 GIT binary patch literal 22138 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

j;8>VJJN@5vU4J0$n7bxmSv$Dx73(t# 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 literal 0 HcmV?d00001