
Votre pipeline s'exécute sur chaque push, peu importe la branche. Un développeur qui pousse 10 commits sur une branche de feature déclenche 10 builds Docker complets et 10 docker push — pour rien. Et le déploiement en production s'exécute automatiquement dès qu'un commit atterrit sur main, sans validation humaine. Dans ce lab, vous allez mettre des conditions d'exécution sur le pipeline et les jobs pour qu'ils ne tournent qu'au bon moment.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre
workflow: rulespour contrôler quand un pipeline démarre - Écrire des
rules:par job avec des conditions sur la branche, l'événement ou les fichiers modifiés - Créer un job manuel avec
when: manualpour valider un déploiement en production - Distinguer un pipeline de Merge Request d'un pipeline de branche
Quel lab commencer ?
Section intitulée « Quel lab commencer ? »Ce lab est la suite logique du Lab 05. Si vous débutez sur GitLab CI/CD, faites 01 à 05 avant de traiter les rules. Si vous êtes déjà à l'aise avec la syntaxe YAML, vous pouvez démarrer directement sur starter/lab-06.
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Un pipeline sans condition d'exécution est un pipeline qui fait trop de choses trop souvent. Sur un projet avec 5 développeurs qui poussent chacun 5 commits par jour sur leurs branches, ça représente 25 builds Docker inutiles chaque jour. Les runners partagés de gitlab.com ont des quotas de minutes CI — mieux vaut ne pas les gaspiller.
Plus important encore : un déploiement en production qui se déclenche automatiquement à chaque merge est risqué. En pratique, toutes les équipes mettent un frein au moins sur la production : quelqu'un doit valider manuellement avant que le code parte en prod.
Situations réelles où ce lab vous aide :
- Votre quota de minutes CI est épuisé mi-mois à cause de builds inutiles sur des branches de feature
- Un merge accidentel sur
maina déclenché un déploiement en production avant que vous puissiez réagir - Vous voulez que le lint et les tests tournent sur les MR, avec un build Docker seulement quand des fichiers critiques changent
- Vous cherchez à ajouter un bouton « déployer en production » dans GitLab sans plugins supplémentaires
Prérequis
Section intitulée « Prérequis »- Lab 05 complété — ou partir directement de la branche
starter/lab-06 - Avoir lu Rules GitLab CI/CD (conseillé)
Point de départ
Section intitulée « Point de départ »-
Passez sur la branche de départ
Fenêtre de terminal cd pipeline-craftgit checkout starter/lab-06 -
Poussez pour déclencher le pipeline
Fenêtre de terminal git push origin starter/lab-06Le pipeline démarre sur cette branche de feature — et déclenche un
docker pushcomplet, alors que vous ne testez que des règles CI. C'est exactement le comportement que vous allez corriger.
Le problème
Section intitulée « Le problème »Le .gitlab-ci.yml de départ ne contient aucune condition d'exécution. Chaque push, sur n'importe quelle branche, déclenche l'intégralité du pipeline : lint, test, build et push Docker.
Le pipeline dont vous avez besoin doit se comporter différemment selon le contexte :
| Contexte | Lint + Tests | Build Docker | Déploiement staging | Déploiement prod |
|---|---|---|---|---|
| Push sur une branche de feature | ✅ | ❌ | ❌ | ❌ |
| Merge Request ouverte | ✅ | ✅ si fichiers critiques modifiés | ❌ | ❌ |
Push sur main | ✅ | ✅ | ✅ (auto) | ✅ (manuel) |
| Tag Git | ❌ | ✅ | ❌ | ❌ |
Pour obtenir ce comportement, vous allez utiliser deux mécanismes de GitLab CI : les workflow: rules (qui contrôlent si un pipeline démarre) et les rules: par job (qui contrôlent si un job s'exécute dans un pipeline démarré).
L'exercice
Section intitulée « L'exercice »Étape 1 — À vous de contrôler quand le pipeline démarre
Section intitulée « Étape 1 — À vous de contrôler quand le pipeline démarre »-
Ajoutez un bloc
workflow: rulesen haut du fichier, aprèsstages:etvariables:. -
Définissez trois cas de démarrage du pipeline :
- pipeline de Merge Request
- push sur la branche par défaut
- push de tag Git
-
Vérifiez le comportement attendu :
- push feature sans MR => pas de pipeline
- ouverture de MR => pipeline déclenché
👉 Vérifier votre solution (Étape 1)
1️⃣ Ajouter un workflow: rules global
Section intitulée « 1️⃣ Ajouter un workflow: rules global »Placez ce bloc après stages: et avant les jobs :
workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG2️⃣ Ce que fait chaque règle
Section intitulée « 2️⃣ Ce que fait chaque règle »$CI_PIPELINE_SOURCE == "merge_request_event": démarre un pipeline de Merge Request$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH: démarre un pipeline surmain$CI_COMMIT_TAG: démarre un pipeline pour un tag Git
3️⃣ Comportement attendu
Section intitulée « 3️⃣ Comportement attendu »| Contexte | Pipeline démarre ? |
|---|---|
Push sur feature/foo sans MR | ❌ Non |
| Ouverture ou mise à jour d'une MR | ✅ Oui |
Push direct sur main | ✅ Oui |
| Création d'un tag Git | ✅ Oui |
Le point important : workflow: décide si GitLab crée un pipeline. Si aucune règle ne matche, le pipeline n'existe même pas.
Étape 2 — À vous d'ajouter des rules par job
Section intitulée « Étape 2 — À vous d'ajouter des rules par job »-
Ajoutez des
rules:surruff-lintetpytestpour les exécuter sur :- Merge Request
- branche par défaut
-
Ajoutez des
rules:plus restrictives surdocker-build:- exécution sur branche par défaut
- déclenchement aussi quand des fichiers critiques changent
-
Vérifiez la logique de sélection :
- lint/tests restent actifs sur MR
- build Docker reste lié à
mainou à une modification applicative significative
👉 Vérifier votre solution (Étape 2)
1️⃣ Ajouter des rules: sur ruff-lint et pytest
Section intitulée « 1️⃣ Ajouter des rules: sur ruff-lint et pytest »ruff-lint: stage: lint image: python:3.12-slim cache: key: files: - requirements-dev.txt paths: - .pip-cache/ policy: pull before_script: - pip install ruff script: - ruff check app/ tests/ rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
pytest: stage: test image: python:3.12-slim cache: key: files: - requirements-dev.txt paths: - .pip-cache/ before_script: - pip install -r requirements-dev.txt script: - pytest -v --junitxml=report.xml artifacts: when: always paths: - report.xml reports: junit: report.xml expire_in: 7 days rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH2️⃣ Restreindre docker-build
Section intitulée « 2️⃣ Restreindre docker-build »docker-build: stage: build image: docker:27 services: - docker:27-dind variables: DOCKER_TLS_CERTDIR: "/certs" before_script: - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - docker push $CI_REGISTRY_IMAGE:latest rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - app/**/* - Dockerfile - requirements.txt - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG3️⃣ Résultat attendu
Section intitulée « 3️⃣ Résultat attendu »ruff-lintetpytesttournent sur MR et surmain- en MR,
docker-buildtourne seulement si les fichiers critiques changent - sur
mainet sur tag,docker-buildtourne systématiquement - une modification de documentation seule ne déclenche pas
docker-builden MR
Dans la branche solution/lab-06, les règles de docker-build sont explicites par contexte : MR ciblée avec changes, branche par défaut, et tag Git.
Étape 3 — À vous d'ajouter un stage de déploiement
Section intitulée « Étape 3 — À vous d'ajouter un stage de déploiement »-
Ajoutez le stage
deploydans la liste des stages. -
Créez un job de déploiement staging avec exécution automatique sur la branche par défaut.
-
Créez un job de déploiement production en mode manuel sur la branche par défaut.
-
Validez le résultat attendu : staging auto, production avec bouton manuel.
👉 Vérifier votre solution (Étape 3)
1️⃣ Ajouter le stage deploy
Section intitulée « 1️⃣ Ajouter le stage deploy »stages: - lint - test - build - deploy2️⃣ Déploiement staging automatique
Section intitulée « 2️⃣ Déploiement staging automatique »deploy-staging: stage: deploy image: alpine:3.20 script: - echo "Deploying $CI_COMMIT_SHORT_SHA to staging..." - ./scripts/deploy-demo.sh staging rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH3️⃣ Déploiement production manuel
Section intitulée « 3️⃣ Déploiement production manuel »deploy-production: stage: deploy image: alpine:3.20 script: - echo "Deploying $CI_COMMIT_SHORT_SHA to production..." - ./scripts/deploy-demo.sh production rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manual4️⃣ Ce que vous devez observer
Section intitulée « 4️⃣ Ce que vous devez observer »- sur
main,deploy-stagingdémarre automatiquement après le build deploy-productionapparaît avec un bouton ▶- tant que personne ne clique, la production ne part pas
when: manual doit être placé dans la règle qui matche le contexte. C'est ce qui ajoute une validation humaine avant l'exécution.
Le fichier complet
Section intitulée « Le fichier complet »📄 Voir le fichier .gitlab-ci.yml complet
stages: - lint - test - build - deploy
workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG
variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
ruff-lint: stage: lint image: python:3.12-slim cache: key: files: - requirements-dev.txt paths: - .pip-cache/ policy: pull before_script: - pip install ruff script: - ruff check app/ tests/ rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
pytest: stage: test image: python:3.12-slim cache: key: files: - requirements-dev.txt paths: - .pip-cache/ before_script: - pip install -r requirements-dev.txt script: - pytest -v --junitxml=report.xml artifacts: when: always paths: - report.xml reports: junit: report.xml expire_in: 7 days rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
docker-build: stage: build image: docker:27 services: - docker:27-dind variables: DOCKER_TLS_CERTDIR: "/certs" before_script: - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - docker push $CI_REGISTRY_IMAGE:latest rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - app/**/* - Dockerfile - requirements.txt - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG
deploy-staging: stage: deploy image: alpine:3.20 script: - echo "Deploying $CI_COMMIT_SHORT_SHA to staging..." - ./scripts/deploy-demo.sh staging rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-production: stage: deploy image: alpine:3.20 script: - echo "Deploying $CI_COMMIT_SHORT_SHA to production..." - ./scripts/deploy-demo.sh production rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manualPousser et vérifier
Section intitulée « Pousser et vérifier »-
Committez et poussez
Fenêtre de terminal git add .gitlab-ci.ymlgit commit -m "ci: add workflow rules, job rules and deploy stages"git push origin starter/lab-06 -
Observez : aucun pipeline ne démarre
La branche
starter/lab-06n'est pasmainet il n'y a pas de MR ouverte. Leworkflow: rulesfiltre ce push — aucun pipeline ne démarre. -
Ouvrez une Merge Request de
starter/lab-06versmainsur votre forkAllez dans Merge Requests > New merge request. Un pipeline démarre — il exécute
ruff-lintetpytest. Le jobdocker-buildne s'exécute que si la MR modifie des fichiers critiques (app/**/*,Dockerfile,requirements.txt). -
Poussez directement sur
mainpour voir le pipeline completFenêtre de terminal git checkout maingit merge starter/lab-06git push origin mainCette fois, tous les jobs tournent : lint, test, build Docker et déploiement staging. Le job
deploy-productionapparaît avec le bouton ▶ et attend votre clic.
Vérification
Section intitulée « Vérification »- Un push sur
starter/lab-06seul (sans MR) ne déclenche aucun pipeline - Une MR ouverte déclenche lint + tests, et
docker-buildseulement si des fichiers critiques changent - Un push sur
maindéclenche lint + tests + build Docker + deploy staging - Le job
deploy-productionaffiche un bouton ▶ et ne s'exécute pas automatiquement
Pièges fréquents
Section intitulée « Pièges fréquents »| Symptôme | Cause | Solution |
|---|---|---|
| Pipeline ne démarre jamais | workflow: rules trop restrictif | Vérifier que la branche ou l'événement correspond à une règle du workflow: |
| Job toujours skipped | rules: ne correspond à aucun contexte actuel | Vérifier les valeurs des variables avec `env |
deploy-production démarre automatiquement | when: manual absent de la règle | Ajouter when: manual sur la ligne de la règle (pas au niveau du job) |
| Double pipeline sur les MR | workflow: manquant — GitLab crée un pipeline de MR ET un pipeline de branche | Ajouter workflow: rules pour éviter les doublons |
Pour aller plus loin
Section intitulée « Pour aller plus loin »only/exceptvsrules:: les anciennes documentations utilisentonly:etexcept:. Ces mots-clés sont dépréciés — utilisez toujoursrules:qui est plus flexible et plus lisible.- Comparez avec
solution/lab-06:git diff origin/starter/lab-06..origin/solution/lab-06montre les modifications complètes.
À retenir
Section intitulée « À retenir »workflow: rulescontrôle si un pipeline démarre.rules:par job contrôle si un job s'exécute dans un pipeline démarré. Les deux se complètent.- Les variables clés :
$CI_PIPELINE_SOURCE(source du pipeline),$CI_DEFAULT_BRANCH(branche principale),$CI_COMMIT_TAG(tag Git) when: manualsur une règle ajoute un bouton ▶ dans l'interface — le job ne démarre que sur clic humain. C'est la façon la plus simple d'ajouter une validation avant un déploiement- La règle
changes:évite des builds inutiles quand seule la documentation change