
Vous poussez une branche, ouvrez une merge request, et deux pipelines tournent en parallèle ? C’est le problème le plus courant quand on débute avec GitLab CI/CD. Le mot-clé workflow:rules permet de décider au niveau global quel type de pipeline créer selon le contexte : push sur une branche, ouverture d’une MR ou création d’un tag.
Sans workflow structuré, vous gaspillez des minutes de CI, polluez l’historique des pipelines et perdez en lisibilité. Ce guide vous donne les patterns éprouvés pour chaque situation.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre pourquoi GitLab crée parfois deux pipelines (branche + MR)
- Utiliser
workflow:rulespour contrôler la création du pipeline au niveau global - Implémenter les trois workflows standard : branch-only, MR-only, hybride
- Configurer un workflow release avec tags sémantiques
- Activer les merge trains pour sérialiser les intégrations
- Éviter les anti-patterns classiques (doublons, déploiement depuis une MR)
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Vous avez un pipeline fonctionnel mais des questions reviennent régulièrement :
- Pourquoi deux pipelines tournent quand j’ouvre une MR ?
- Comment lancer un pipeline uniquement sur les merge requests ?
- Comment déclencher un déploiement uniquement quand je crée un tag
v1.2.3? - Comment empêcher le pipeline de tourner sur les branches de feature inutiles ?
Ce guide répond à toutes ces questions avec des patterns concrets et testés.
Le problème des doublons
Section intitulée « Le problème des doublons »Pourquoi deux pipelines ?
Section intitulée « Pourquoi deux pipelines ? »Par défaut, GitLab crée un pipeline pour chaque événement :
| Événement | Pipeline créé | CI_PIPELINE_SOURCE |
|---|---|---|
git push sur une branche | ✅ | push |
| Ouverture d’une MR | ✅ | merge_request_event |
| Mise à jour d’une branche avec une MR ouverte | ✅✅ | push ET merge_request_event |
| Création d’un tag | ✅ | push |
| Planification (schedule) | ✅ | schedule |
Le piège : quand vous poussez sur une branche qui a une MR ouverte, GitLab crée deux pipelines — un pour le push, un pour la MR. Si vos jobs n’ont pas de rules, ils tournent dans les deux.
CI_OPEN_MERGE_REQUESTS
Section intitulée « CI_OPEN_MERGE_REQUESTS »GitLab fournit la variable CI_OPEN_MERGE_REQUESTS qui contient la liste des MR ouvertes pour la branche courante. Elle permet d’éviter les doublons :
job: rules: # Ne pas tourner sur les push qui ont déjà une MR ouverte - if: $CI_PIPELINE_SOURCE == "push" && $CI_OPEN_MERGE_REQUESTS when: never - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHMais cette approche job par job est fragile. La solution robuste : workflow:rules.
workflow:rules — le gardien global
Section intitulée « workflow:rules — le gardien global »workflow:rules décide si le pipeline est créé ou non, avant même d’évaluer les rules de chaque job. C’est le premier filtre.
workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAGCe pipeline ne sera créé que pour :
- Les événements merge request
- Les push sur la branche par défaut (main)
- Les tags
Tout le reste est ignoré : push sur les branches de feature (sauf si une MR existe), forks, etc.
Les trois workflows standard
Section intitulée « Les trois workflows standard »1. Branch pipeline (le plus simple)
Section intitulée « 1. Branch pipeline (le plus simple) »Chaque push crée un pipeline. Pas de notion de MR.
workflow: rules: - if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_TAG
# Tous les jobs tournent sur chaque pushlint: script: npm run lint
test: script: npm testQuand l’utiliser : projets personnels, petites équipes sans process de review.
Inconvénient : pas de pipeline dédié MR, pas de review visuelle dans la MR.
2. MR pipeline (le plus courant en équipe)
Section intitulée « 2. MR pipeline (le plus courant en équipe) »Le pipeline tourne uniquement sur les merge requests et la branche par défaut.
workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAGCe que ça donne :
| Action | Pipeline ? |
|---|---|
Push sur feature/xxx (sans MR) | ❌ |
Ouverture MR feature/xxx → main | ✅ (source: merge_request_event) |
Push sur feature/xxx avec MR ouverte | ✅ (source: merge_request_event) |
Merge dans main | ✅ (source: push sur default branch) |
Tag v1.0.0 | ✅ (source: push tag) |
Quand l’utiliser : la majorité des projets en équipe. Pas de doublons, pipeline visible dans la MR.
3. Workflow hybride (branch + MR)
Section intitulée « 3. Workflow hybride (branch + MR) »Variante pour ceux qui veulent un pipeline même sur les branches sans MR (pour le retour rapide) :
workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS when: never - if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_TAGLogique :
- Si c’est un événement MR → pipeline MR ✅
- Si c’est un push et qu’une MR est ouverte → pas de pipeline (la MR s’en charge) ❌
- Si c’est un push sans MR → pipeline branch ✅
- Si c’est un tag → pipeline ✅
Ce pattern donne un pipeline dès le premier push (retour rapide) puis bascule sur le pipeline MR dès qu’une MR est ouverte. Aucun doublon.
Workflow release avec tags
Section intitulée « Workflow release avec tags »Semantic versioning
Section intitulée « Semantic versioning »La convention : les déploiements en production sont déclenchés par des tags au format vX.Y.Z.
deploy:production: stage: deploy environment: name: production url: https://www.example.com script: - echo "Deploying $CI_COMMIT_TAG" - ./deploy.sh production rules: - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ when: manual allow_failure: false resource_group: productionPipeline complet avec release
Section intitulée « Pipeline complet avec release »workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG =~ /^v\d+/
stages: - test - build - deploy - release
test: stage: test script: npm test rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG
build: stage: build script: npm run build artifacts: paths: [dist/] rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG
deploy:staging: stage: deploy environment: name: staging script: ./deploy.sh staging rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy:production: stage: deploy environment: name: production script: ./deploy.sh production rules: - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ when: manual resource_group: production
create:release: stage: release image: registry.gitlab.com/gitlab-org/release-cli:latest script: - echo "Creating release for $CI_COMMIT_TAG" release: tag_name: $CI_COMMIT_TAG name: "Release $CI_COMMIT_TAG" description: "Automatic release" rules: - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/Merged results et merge trains
Section intitulée « Merged results et merge trains »Pipeline for merged results
Section intitulée « Pipeline for merged results »Par défaut, le pipeline MR teste le code de votre branche. Mais au moment du merge, la branche cible a peut-être avancé. Le pipeline merged results teste le résultat anticipé du merge :
Pour l’activer : Settings > Merge requests > Merge pipelines → activer “Pipelines must succeed” + “Merged results pipelines”.
Merge trains
Section intitulée « Merge trains »Les merge trains vont plus loin : quand plusieurs MR sont prêtes à merger, GitLab les sérialise et teste chaque MR en incluant les précédentes :
- MR-A est dans le train → teste
main + MR-A - MR-B arrive → teste
main + MR-A + MR-B - MR-A passe → merge → MR-B reteste si nécessaire
Avantage : la branche main ne casse jamais. Chaque merge est testé avec tout ce qui le précède.
Inconvénient : plus lent, chaque MR attend son tour. Réservé aux branches critiques.
Pour activer : Settings > Merge requests > Merge trains.
workflow: rules: - if: $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train" - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAGReview apps
Section intitulée « Review apps »Les review apps créent un environnement temporaire par MR :
review: stage: deploy environment: name: review/$CI_MERGE_REQUEST_IID url: https://$CI_MERGE_REQUEST_IID.review.example.com on_stop: stop:review auto_stop_in: 1 week script: - ./deploy-review.sh "$CI_MERGE_REQUEST_IID" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event"
stop:review: stage: deploy environment: name: review/$CI_MERGE_REQUEST_IID action: stop script: - ./destroy-review.sh "$CI_MERGE_REQUEST_IID" when: manual rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" when: manualPoints clés :
auto_stop_in: 1 weekdétruit l’environnement automatiquement après 1 semaine- Le nom
review/$CI_MERGE_REQUEST_IIDcrée un environnement unique par MR - L’
on_stopdéclenche le nettoyage à la fermeture de la MR
→ Voir aussi Environnements (V1-10) pour les bases.
Anti-patterns à éviter
Section intitulée « Anti-patterns à éviter »1. Pipeline doublon branch + MR
Section intitulée « 1. Pipeline doublon branch + MR »# ❌ Pas de workflow:rules → doublonstest: script: npm test rules: - if: $CI_COMMIT_BRANCH # Matche sur push ET MR# ✅ workflow:rules filtre en amontworkflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG2. Déploiement depuis une MR
Section intitulée « 2. Déploiement depuis une MR »# ❌ Dangereux : déploie du code non mergédeploy: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event"# ✅ Déploie uniquement après mergedeploy: rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH3. only/except mélangé avec rules
Section intitulée « 3. only/except mélangé avec rules »# ❌ only/except et rules ne se mélangent pastest: only: - branches rules: - if: $CI_COMMIT_TAGUtilisez uniquement rules: (recommandé depuis GitLab 12.3).
4. workflow:rules trop restrictif
Section intitulée « 4. workflow:rules trop restrictif »# ❌ Bloque les schedules et les triggers manuelsworkflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH# ✅ Autoriser aussi les schedules et les triggersworkflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web"Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
| Deux pipelines au même moment | Pas de workflow:rules | Ajouter le workflow MR recommandé |
| Pipeline absent sur une MR | workflow:rules n’inclut pas merge_request_event | Ajouter la règle MR |
| Pipeline sur branche de feature non voulu | workflow:rules accepte tous les push | Ajouter $CI_OPEN_MERGE_REQUESTS check |
| Merge bloqué “pipeline required” | Le pipeline MR est manquant ou en échec | Vérifier que le workflow crée bien un pipeline MR |
| Merge train qui ne démarre pas | Feature non activée dans les settings | Activer dans Settings > Merge requests |
| Variables MR non disponibles dans un pipeline push | $CI_MERGE_REQUEST_IID n’existe que dans les MR pipelines | Utiliser le bon CI_PIPELINE_SOURCE |
À retenir
Section intitulée « À retenir »workflow:rulesest le premier filtre — il décide si le pipeline est créé ou non- Le workflow MR (merge_request_event + default branch + tags) est recommandé pour les équipes
- Sans workflow, GitLab crée des doublons (un pipeline push + un pipeline MR)
- Utilisez
$CI_OPEN_MERGE_REQUESTSpour le pattern hybride (branch puis MR) - Les merged results testent le résultat anticipé du merge, pas la branche seule
- Les merge trains sérialisent les MR pour garder
maintoujours verte - Ne déployez jamais depuis un pipeline MR — uniquement après merge ou sur tag
- Les review apps créent un environnement temporaire par MR avec nettoyage automatique