TP : Conteneurisation de votre application Node.js et intégration dans GitLab CI¶
Ce TP a pour objectif de vous faire pratiquer la création d'images Docker pour une application Node.js et l'automatisation de sa construction dans GitLab CI
Objectifs du TP¶
- Rédiger un Dockerfile pour une application Node.js
- Optimiser une image Docker avec le multi-stage build
- Comprendre et utiliser le Docker-in-Docker (DinD) ou Kaniko dans GitLab CI
- Automatiser la création de l'image Docker dans le pipeline CI
Étape 1 : Création du Dockerfile basique¶
Nous allons créer une image Docker pour l'application Node.js développée au TP précédent
- À la racine de votre projet, créez un fichier nommé
Dockerfile - Utilisez l'image de base
node:20-alpine(plus légère que l'image node standard) - Définissez le répertoire de travail dans le conteneur avec
WORKDIR(par exemple/usr/src/app) - Copiez les fichiers
package.jsonetpackage-lock.jsonouyarn.lock - Exécutez la commande d'installation des dépendances avec
RUN npm ci - Copiez le reste de votre code source dans le conteneur
- Exposez le port 3000 utilisé par votre application avec
EXPOSE - Définissez la commande de démarrage de votre application avec
CMD - N'oubliez pas d'ajouter un fichier
.dockerignorecontenantnode_moduleset.gitpour ne pas alourdir le contexte de build
Étape 2 : Test de l'image localement (Optionnel)¶
Si vous disposez de Docker sur votre machine, vous pouvez vérifier que l'image se construit correctement avant de l'envoyer sur GitLab
- Construisez votre image en lui donnant un nom avec la commande
docker build -t mon-app-node . - Lancez un conteneur à partir de cette image avec
docker run -p 3000:3000 mon-app-node - Vérifiez dans votre navigateur que l'application répond sur
http://localhost:3000
Étape 3 : Optimisation avec le Multi-stage Build¶
Pour avoir une image de production la plus légère possible, nous allons séparer l'étape de build de l'étape d'exécution
- Modifiez votre
Dockerfilepour introduire une première étape nomméebuilder(ex:FROM node:20-alpine as builder) - Dans cette étape
builder, installez toutes les dépendances et générez les fichiers nécessaires au bon fonctionnement de votre application - Créez une seconde étape en utilisant à nouveau
node:20-alpine - Définissez la variable d'environnement
NODE_ENVàproduction - Utilisez l'instruction
COPY --from=builderpour ne récupérer que le code compilé et les dossiers nécessaires depuis l'étape précédente - Pour des raisons de sécurité (principe du moindre privilège), configurez le conteneur pour s'exécuter en tant qu'utilisateur non-privilégié en ajoutant la directive
USER nodejuste avant l'instructionCMD(l'utilisateurnodeest déjà présent dans l'image de base Alpine) - Observez la réduction de taille de l'image finale si vous la buildez localement
Étape 4 : Automatisation de la construction de l'image sur GitLab CI¶
Maintenant que le Dockerfile est prêt, nous allons demander à GitLab CI de construire notre image Docker à chaque modification du code
Nous utiliserons la méthode Kaniko qui est plus sécurisée et recommandée pour les environnements Kubernetes
- Dans votre fichier
.gitlab-ci.yml, ajoutez une étapebuild_image(ou similaire) à vos stages existants - Créez un job
build_docker_imagerattaché à cette nouvelle étape - Utilisez l'image
gcr.io/kaniko-project/executor:debugpour ce job avec l'entrypoint vide[""] - Dans la section script, ajoutez la commande pour configurer l'authentification au registre GitLab via un
echovers/kaniko/.docker/config.json - Lancez l'exécutable Kaniko pour construire l'image en lui fournissant le chemin du Dockerfile (
$CI_PROJECT_DIR/Dockerfile), le contexte ($CI_PROJECT_DIR) et la destination (par exemple$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA) - Commitez vos changements et vérifiez dans l'interface GitLab que le pipeline réussit et que l'image est bien poussée dans le Container Registry de votre projet
Étape 5 : Validation de la qualité et de la sécurité (Linting)¶
Pour garantir la qualité et la sécurité de notre Dockerfile dans un cadre industriel, nous allons ajouter une étape de validation automatique dans notre pipeline CI. Trois approches courantes sont possibles (vous pouvez choisir l'une d'entre elles) :
Option A : Hadolint (Linter historique et spécialisé)¶
- Dans votre
.gitlab-ci.yml, ajoutez une étapelintau tout début de vos stages existants - Créez un job nommé
lint_hadolintrattaché à cette étape - Utilisez l'image officielle
hadolint/hadolint:latest-alpine - Dans la section
script, lancez la commandehadolint Dockerfile
Option B : Trivy (Scanner de vulnérabilités et de conformité)¶
Trivy permet de scanner le Dockerfile pour y détecter des failles de sécurité ou des non-conformités. 1. Créez un job nommé lint_trivy rattaché à l'étape lint 2. Utilisez l'image aquasec/trivy:latest 3. Dans la section script, lancez la commande trivy config Dockerfile (ou trivy config . pour scanner tout le projet)
Option C : Docker build --check (Validation native)¶
Docker permet de vérifier la syntaxe et le respect des bonnes pratiques directement via la CLI sans construire l'image. 1. Créez un job nommé lint_docker_check rattaché à l'étape lint 2. Utilisez l'image docker:latest avec le service docker:dind 3. Définissez la variable DOCKER_HOST à tcp://docker:2375 4. Dans la section script, lancez la commande docker build --check .
Exercice : Choisissez et configurez l'une des méthodes ci-dessus, puis poussez vos modifications et vérifiez le résultat dans GitLab CI. Si des alertes sont remontées, corrigez votre Dockerfile en conséquence
Étape 6 : Optimisation du cache Kaniko et stratégie de Tagging¶
Dans un contexte professionnel, il est crucial de réduire le temps d'exécution des pipelines et d'adopter un nommage clair et structuré de nos images Docker
- Modifiez le script de votre job Kaniko pour y ajouter l'argument de cache
--cache=trueainsi que la destination du cache--cache-repo=$CI_REGISTRY_IMAGE/cache - Lancez le pipeline deux fois de suite sans modifier le Dockerfile et observez la réduction du temps d'exécution lors du second passage
- Modifiez la destination du build pour pousser deux tags différents à chaque build : l'image avec le SHA du commit (
$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA) et une image avec le nom de la branche ($CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG) (Kaniko permet de spécifier plusieurs arguments--destination) - Optionnel : Ajoutez une règle de pipeline (
rules) pour que l'image soit tagguée avec la version de la release ($CI_REGISTRY_IMAGE:$CI_COMMIT_TAG) uniquement lorsqu'un tag Git (ex:v1.0.0) est créé
Étape 7 : Publication sur un registre externe privé (Docker Hub)¶
Dans de nombreux projets industriels, les images ne sont pas stockées sur le registre GitLab intégré, mais sur un registre externe d'entreprise (Docker Hub, Harbor, AWS ECR, etc.). Nous allons configurer notre pipeline pour pousser sur Docker Hub.
- Créez un compte gratuit sur Docker Hub si vous n'en avez pas déjà un
- Générez un jeton d'accès personnel (Personal Access Token - PAT) dans les paramètres de votre compte Docker Hub (onglet Security > Access Tokens)
- Dans GitLab, allez dans Settings > CI/CD > Variables et ajoutez deux variables masquées :
DOCKER_HUB_USER: Votre nom d'utilisateur Docker HubDOCKER_HUB_TOKEN: Le jeton d'accès généré (ne jamais utiliser votre mot de passe en clair dans la CI)- Modifiez votre job Kaniko (ou créez-en un nouveau) pour qu'il génère le fichier
/kaniko/.docker/config.jsonavec les informations d'authentification de Docker Hub (l'URL du registre par défaut esthttps://index.docker.io/v1/) - Modifiez le paramètre
--destinationde Kaniko pour cibler votre dépôt Docker Hub :docker.io/<votre_utilisateur_dockerhub>/<nom_du_depot>:$CI_COMMIT_SHA - Lancez le pipeline et vérifiez que l'image est bien présente sur votre compte Docker Hub
Étape 8 : Bonus - Exploration d'une alternative moderne (Buildah ou BuildKit)¶
Pour dépasser les limites de sécurité de DinD et préparer l'après-Kaniko, essayez de configurer un job de build utilisant l'un des standards modernes de l'industrie
Option A : Utilisation de Buildah (Recommandé)¶
- Créez un job
build_buildahdans votre.gitlab-ci.yml - Utilisez l'image
quay.io/buildah/stable:latest - Dans la section
script, connectez-vous au registre GitLab avecbuildah login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - Lancez la construction de l'image avec
buildah bud -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - Poussez l'image avec
buildah push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Option B : Utilisation de BuildKit Rootless¶
- Créez un job
build_buildkitdans votre.gitlab-ci.yml - Utilisez l'image
moby/buildkit:latest-rootlessavec l'entrypoint vide[""] - Définissez la variable
BUILDKITD_FLAGSà--oci-worker-no-process-sandbox - Configurez le fichier d'authentification
~/.docker/config.jsonavec vos identifiants de registre - Lancez la commande de build sans démon :
buildctl-daemonless.sh build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
Option C : Utilisation de Cloud Native Buildpacks (Zéro Dockerfile)¶
Les Buildpacks permettent de construire une image sans écrire ni maintenir de Dockerfile, en détectant automatiquement la pile technologique (Node.js). 1. Créez un job build_buildpack dans votre .gitlab-ci.yml 2. Utilisez l'image buildpacksio/pack:latest et déclarez le service docker:dind (la CLI pack a besoin d'un démon Docker pour lancer le build) 3. Définissez la variable DOCKER_HOST à tcp://docker:2375 4. Dans la section script, connectez-vous au registre GitLab avec docker login (comme vu précédemment) 5. Lancez la construction et la publication automatique avec la commande : pack build $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --builder paketo-buildpacks/builder-jammy-base --publish