From 1517215b1983f1009a05946cdeae12b3e023ea3a Mon Sep 17 00:00:00 2001 From: hadestructhor <60148800+hadestructhor@users.noreply.github.com> Date: Sat, 4 Jan 2025 21:39:24 +0100 Subject: [PATCH] feat: nginx Dockerfile --- .woodpecker/.workflow.yml | 10 +---- Dockerfile | 9 ++-- README.md | 85 +++++++++++++++++++++++++++++++++++- assets/react-nginx-size.png | Bin 0 -> 19151 bytes 4 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 assets/react-nginx-size.png diff --git a/.woodpecker/.workflow.yml b/.woodpecker/.workflow.yml index 498fb10..665d311 100644 --- a/.woodpecker/.workflow.yml +++ b/.woodpecker/.workflow.yml @@ -1,16 +1,10 @@ when: - event: [ tag, manual, push, pull_request ] - branch: main steps: - - name: npm install - image: node:18 - commands: - - npm install - name: docker build and publish when: - event: [tag, manual, push, pull_request] - branch: main image: docker environment: DOCKER_USERNAME: @@ -21,5 +15,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-multistage-alpine:latest' . - - docker push forgejo.transprot.eu/public/react-multistage-alpine:latest \ No newline at end of file + - docker build --no-cache -t 'forgejo.transprot.eu/public/react-nginx:latest' . + - docker push forgejo.transprot.eu/public/react-nginx:latest \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a543662..f11e443 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,7 @@ COPY . . RUN npm run build # Production stage to run the application -FROM node:18-alpine AS production -COPY --from=build /app/build /prod -RUN npm install --global serve -EXPOSE 3000 -CMD ["serve", "prod/"] \ No newline at end of file +FROM nginx 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 613c680..ecd9a37 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,48 @@ And the best in all of this ? Not only is the image smaller, but it is way more But we can go even further beyond in optimizing this image. +### Third optimization: Using a better http server: Nginx + +The next optimization that we can perform is using a nginx server instead of a node server to run our application. +This will serve the static build files faster than the node server and it will also be better perfomance, memory and requests wise as node performs worse than nginx to serve files under the same conditions. + +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 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' . +``` + +To run this image, you will need to run the following command: +```shell +docker run -d -p 80:80 --name react react-nginx +``` + +And you can also run the following command to check the size of the image: +```shell +docker image ls +``` + +But there's a tiny problem with this solution, this only optimizes the perfomance of the image but the size of the regular Nginx server is bigger than the Node Alpine version. + +Here's an image showing the size of all the images so far: +![Nginx image size](./assets/react-nginx-size.png) + # Français @@ -298,4 +340,45 @@ On a une réduction de `1.82 GB`, soit une image 9.8 fois plus petite et une ré Et le meilleur dans tout ça ? Non seulement l'image est plus petite, mais elle est plus performante car elle ne contient que le strict minimum nécessaire pour faire tourner node via la version alpine. -Mais on peut encore aller plus loins dans l'optimisation. \ No newline at end of file +Mais on peut encore aller plus loins dans l'optimisation. +### Troisième optimisation : Utilisation d'un meilleur serveur http: Nginx + +La prochaine optimisation que l'on peut effectuer est l'utilisation de Nginx comme serveur pour servir notre application React plutôt que Node. +Les fichiers statiques construits de notre application seront envoyés aux utilisateurs plus rapidement, et le serveur utilisera moins de mémoire , performera mieux et pourra traiter plus de requête que le serveur node car nginx est plus performant sous des conditions similaires. + +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 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' . +``` + +Pour démarrer un conteneur, vous pouvez lancer la commande suivante: +```shell +docker run -d -p 80:80 --name react react-nginx +``` + +Vous pouvez également vérifier la taille de l'image en lançant cette commande: +```shell +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 diff --git a/assets/react-nginx-size.png b/assets/react-nginx-size.png new file mode 100644 index 0000000000000000000000000000000000000000..c6f2bcd513fc6e97f34430044d89aa986df01c26 GIT binary patch literal 19151 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�Cj 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