Aller au contenu
CI/CD & Automatisation medium

Workflows CI/CD GitLab (MR, branches, releases)

18 min de lecture

logo gitlab

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.

  • Comprendre pourquoi GitLab crée parfois deux pipelines (branche + MR)
  • Utiliser workflow:rules pour 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)

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.

Avant de corriger, il faut comprendre d'où vient le doublon. GitLab ne raisonne pas en « une branche, un pipeline » mais en événements : chaque action sur le dépôt déclenche son propre pipeline, avec sa propre valeur de CI_PIPELINE_SOURCE. Deux événements simultanés donnent donc mécaniquement deux pipelines, et non un pipeline dédoublé par erreur.

Par défaut, GitLab crée un pipeline pour chaque événement :

ÉvénementPipeline crééCI_PIPELINE_SOURCE
git push sur une branchepush
Ouverture d'une MRmerge_request_event
Mise à jour d'une branche avec une MR ouverte✅✅push ET merge_request_event
Création d'un tagpush
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.

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_BRANCH

Mais cette approche job par job est fragile. La solution robuste : workflow:rules.

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_TAG

Ce pipeline ne sera créé que pour :

  1. Les événements merge request
  2. Les push sur la branche par défaut (main)
  3. Les tags

Tout le reste est ignoré : push sur les branches de feature (sauf si une MR existe), forks, etc.

Presque tous les projets se rangent dans l'un des trois modèles ci-dessous. Ils se distinguent par un seul critère : quels événements créent un pipeline. Le premier accepte tous les push, le deuxième ne retient que les merge requests et la branche par défaut, le troisième combine les deux en excluant explicitement le cas qui produirait un doublon. Choisissez selon la manière dont votre équipe travaille, pas selon la complexité de la configuration.

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 push
lint:
script: npm run lint
test:
script: npm test

Quand 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.

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_TAG

Ce que ça donne :

ActionPipeline ?
Push sur feature/xxx (sans MR)
Ouverture MR feature/xxxmain✅ (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.

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_TAG

Logique :

  1. Si c'est un événement MR → pipeline MR ✅
  2. Si c'est un push et qu'une MR est ouverte → pas de pipeline (la MR s'en charge) ❌
  3. Si c'est un push sans MR → pipeline branch ✅
  4. 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.

Une fois les doublons réglés, reste la question du déploiement. Le tag est le déclencheur le plus sûr : contrairement à une branche, il désigne un commit précis et immuable, ce qui rend la version déployée traçable. GitLab expose le tag dans la variable $CI_COMMIT_TAG, absente de tous les autres types de pipeline.

La convention retenue ici : les déploiements en production sont déclenchés par des tags au format vX.Y.Z. L'expression régulière /^v\d+\.\d+\.\d+$/ est volontairement stricte, ancrée au début et à la fin, pour qu'un tag de travail comme v2-rc1 ne parte pas en production. Le when: manual ajoute une validation humaine, et resource_group: production empêche deux déploiements de tourner en même temps sur le même environnement.

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: production

L'exemple ci-dessous rassemble tout : le filtre global, puis des rules: par job qui restreignent encore. Repérez la progression, elle est volontaire. test tourne partout, build seulement sur la branche par défaut et les tags, deploy:production uniquement sur un tag de version. Un job sans règle correspondante n'apparaît tout simplement pas dans le pipeline.

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:v0.24.0
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+$/

Ces deux fonctionnalités répondent au même angle mort : un pipeline MR valide le code tel qu'il est aujourd'hui, alors que le merge aura lieu sur une branche cible qui aura peut-être bougé entre-temps. Elles se cumulent, les merge trains s'appuyant sur les merged results. Toutes deux exigent une édition Premium et se règlent dans les paramètres du projet, pas dans le fichier .gitlab-ci.yml.

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".

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 :

  1. MR-A est dans le train → teste main + MR-A
  2. MR-B arrive → teste main + MR-A + MR-B
  3. 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_TAG

Les review apps créent un environnement temporaire par merge request, ce qui permet à un relecteur de cliquer sur la fonctionnalité au lieu de la deviner dans un diff. Le mécanisme repose entièrement sur le mot-clé environment : un nom dynamique bâti sur $CI_MERGE_REQUEST_IID crée un environnement distinct par MR, et GitLab affiche le lien directement dans l'interface de la merge request. Le risque, avec ce modèle, est l'accumulation d'environnements abandonnés, d'où les deux mécanismes de nettoyage présents dans l'exemple : on_stop et auto_stop_in.

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: manual

Points clés :

  • auto_stop_in: 1 week détruit l'environnement automatiquement après 1 semaine
  • Le nom review/$CI_MERGE_REQUEST_IID crée un environnement unique par MR
  • L'on_stop déclenche le nettoyage à la fermeture de la MR

Voir aussi Environnements (V1-10) pour les bases.

Ces quatre erreurs reviennent constamment dans les .gitlab-ci.yml d'équipes expérimentées. Aucune ne provoque d'erreur de syntaxe : le pipeline se lance, mais il fait autre chose que ce qui était prévu. Chaque cas montre la version fautive puis sa correction.

Sans workflow:rules, GitLab décide de créer les deux pipelines avant de lire les rules: des jobs. Ajuster ces règles job par job ne supprime donc jamais le pipeline en trop : il continue d'apparaître dans l'historique, vide ou partiel. Le filtre doit être posé au niveau global.

# ❌ Pas de workflow:rules → doublons
test:
script: npm test
rules:
- if: $CI_COMMIT_BRANCH # Matche sur push ET MR
# ✅ workflow:rules filtre en amont
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG

Le code d'une merge request n'a pas encore été relu ni fusionné, et il peut provenir d'un fork. Déployer depuis ce pipeline revient à mettre en ligne du code que personne n'a validé. Réservez le déploiement à la branche par défaut ou à un tag.

# ❌ Dangereux : déploie du code non mergé
deploy:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# ✅ Déploie uniquement après merge
deploy:
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

only/except et rules sont deux syntaxes concurrentes, héritées de deux générations de GitLab CI. Les mélanger dans un même job produit une erreur de validation du fichier, pas un comportement dégradé : le pipeline ne démarre pas.

# ❌ only/except et rules ne se mélangent pas
test:
only:
- branches
rules:
- if: $CI_COMMIT_TAG

Utilisez uniquement rules: (recommandé depuis GitLab 12.3).

Le piège inverse du premier. Un workflow:rules qui ne liste que les MR et la branche par défaut fait disparaître silencieusement les pipelines planifiés (schedule) et ceux lancés depuis l'interface (web). Aucun message d'erreur : le pipeline n'est pas créé, et la sauvegarde nocturne ne tourne plus.

# ❌ Bloque les schedules et les triggers manuels
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# ✅ Autoriser aussi les schedules et les triggers
workflow:
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"

La quasi-totalité des symptômes ci-dessous se diagnostique au même endroit : la valeur de $CI_PIPELINE_SOURCE dans le pipeline concerné, comparée aux conditions de votre workflow:rules. Affichez-la dans un job avec echo $CI_PIPELINE_SOURCE avant de modifier quoi que ce soit, cela évite de corriger la mauvaise règle.

SymptômeCause probableSolution
Deux pipelines au même momentPas de workflow:rulesAjouter le workflow MR recommandé
Pipeline absent sur une MRworkflow:rules n'inclut pas merge_request_eventAjouter la règle MR
Pipeline sur branche de feature non vouluworkflow:rules accepte tous les pushAjouter $CI_OPEN_MERGE_REQUESTS check
Merge bloqué "pipeline required"Le pipeline MR est manquant ou en échecVérifier que le workflow crée bien un pipeline MR
Merge train qui ne démarre pasFeature non activée dans les settingsActiver dans Settings > Merge requests
Variables MR non disponibles dans un pipeline push$CI_MERGE_REQUEST_IID n'existe que dans les MR pipelinesUtiliser le bon CI_PIPELINE_SOURCE
  • workflow:rules est 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_REQUESTS pour 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 main toujours 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

Appliquez ces règles dans le Lab 17, Workflows branches et MR.

Dix questions pour vérifier que les notions de ce guide sont acquises : origine des doublons, ordre d'évaluation des règles et choix du workflow selon le contexte.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
5 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn