Aller au contenu
CI/CD & Automatisation medium

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

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

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.

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.

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: production
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+$/

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

# ❌ 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
# ❌ 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 ne se mélangent pas
test:
only:
- branches
rules:
- if: $CI_COMMIT_TAG

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

# ❌ 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"
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

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn