
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
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, mais que le build et le déploiement n’aient lieu que sur
main - 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 | ✅ | ❌ | ❌ | ❌ |
Push sur main | ✅ | ✅ | ✅ (auto) | ✅ (manuel) |
| Tag Git | ❌ | ✅ | ❌ | ✅ (manuel) |
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 — Contrôler quand le pipeline démarre
Section intitulée « Étape 1 — Contrôler quand le pipeline démarre »-
Ajoutez un bloc
workflow: rulesen haut du fichier, aprèsstages:etvariables:workflow:rules:- if: $CI_MERGE_REQUEST_IID- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH- if: $CI_COMMIT_TAGCe bloc définit les trois seuls cas où un pipeline doit démarrer :
$CI_MERGE_REQUEST_IID: cette variable est définie quand le push fait partie d’une Merge Request ouverte. Le pipeline associé s’appelle un “MR pipeline”.$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH:$CI_DEFAULT_BRANCHvautmainpar défaut. Cette règle déclenche le pipeline uniquement quand on pousse surmain.$CI_COMMIT_TAG: cette variable est définie quand on pousse un tag Git (git tag v1.0 && git push --tags). Utile pour déclencher des déploiements sur des releases.
Conséquence : un développeur qui pousse sur
feature/ma-fonctionnalitesans MR ouverte ne déclenche aucun pipeline. Dès qu’il ouvre une MR, le pipeline démarre.
Étape 2 — Ajouter des rules par job
Section intitulée « Étape 2 — Ajouter des rules par job »-
Ajoutez des
rules:sur le jobruff-lintLe lint doit tourner sur les MR et sur
main:ruff-lint:stage: lintimage: python:3.12-slimcache:key:files:- requirements-dev.txtpaths:- .pip-cache/policy: pullbefore_script:- pip install ruffscript:- ruff check app/ tests/rules:- if: $CI_MERGE_REQUEST_IID- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH -
Ajoutez les mêmes
rules:sur le jobpytestpytest:...rules:- if: $CI_MERGE_REQUEST_IID- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH -
Ajoutez des
rules:plus restrictives sur le jobdocker-buildLe build Docker ne doit se déclencher que sur
mainou quand des fichiers critiques changent :docker-build:stage: buildimage: docker:27services:- docker:27-dindvariables:DOCKER_TLS_CERTDIR: "/certs"script:- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .- docker build -t $CI_REGISTRY_IMAGE:latest .- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA- docker push $CI_REGISTRY_IMAGE:latestrules:- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH- changes:- app/**/*- Dockerfile- requirements.txtLa règle
changes:est intéressante : elle déclenche le job uniquement si l’un des fichiers listés a été modifié dans le commit. Si vous poussez surmainen ne modifiant que la documentation, le build Docker ne tourne pas.
Étape 3 — Ajouter un stage de déploiement
Section intitulée « Étape 3 — Ajouter un stage de déploiement »-
Ajoutez
deploydans la liste des stagesstages:- lint- test- build- deploy -
Ajoutez le job de déploiement en staging (automatique sur
main)deploy-staging:stage: deployimage: alpine:3.20script:- echo "Deploying $CI_COMMIT_SHORT_SHA to staging..."- ./scripts/deploy-demo.sh stagingrules:- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHCe job utilise
alpine:3.20, une image très légère (~5 Mo) adaptée aux scripts shell simples. Le scriptdeploy-demo.shest fourni dans le dépôt — il simule un déploiement (n’ouvre pas de vraie connexion). -
Ajoutez le job de déploiement en production (manuel sur
main)deploy-production:stage: deployimage: alpine:3.20script:- echo "Deploying $CI_COMMIT_SHORT_SHA to production..."- ./scripts/deploy-demo.sh productionrules:- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHwhen: manualLa clé
when: manualest le détail crucial. Sans elle, le job démarre automatiquement. Avec elle, GitLab affiche un bouton ▶ dans l’interface — le job ne démarre que quand quelqu’un clique dessus. C’est la façon la plus simple d’ajouter une validation humaine avant un déploiement en production.
Le fichier complet
Section intitulée « Le fichier complet »stages: - lint - test - build - deploy
workflow: rules: - if: $CI_MERGE_REQUEST_IID - 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_MERGE_REQUEST_IID - 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_MERGE_REQUEST_IID - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
docker-build: stage: build image: docker:27 services: - docker:27-dind variables: DOCKER_TLS_CERTDIR: "/certs" script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker build -t $CI_REGISTRY_IMAGE:latest . - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - docker push $CI_REGISTRY_IMAGE:latest rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - changes: - app/**/* - Dockerfile - requirements.txt
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, mais pasdocker-buildni les jobs de déploiement. -
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 uniquement lint + tests
- Un push sur
maindéclenche lint + tests + build Docker + deploy staging - Le job
deploy-productionaffiche un bouton ▶ et ne s’exécute pas automatiquement
Ce qui a changé
Section intitulée « Ce qui a changé »stages: - lint - test - build - deploy
workflow: rules: - if: $CI_MERGE_REQUEST_IID - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG
ruff-lint: ... rules: - if: $CI_MERGE_REQUEST_IID - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
pytest: ... rules: - if: $CI_MERGE_REQUEST_IID - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
docker-build: ... rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - changes: - app/**/* - Dockerfile - requirements.txt
deploy-staging: stage: deploy ... rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-production: stage: deploy ... rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manualPiè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 starter/lab-06..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_MERGE_REQUEST_IID(pipeline de MR),$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