Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Matrices de jobs GitLab CI/CD (parallel:matrix)

Par mis à jour le

Améliorer cette page

Logo GitLab

Vous devez tester sur Node 22, 24 et 26 sans copier-coller trois fois le même job ? Avec parallel:matrix, un seul job définit la logique, et GitLab génère automatiquement une instance par combinaison de variables. Plus de duplication, plus de risque d'oubli.

parallel vs parallel:matrix. Deux mots-clés, deux usages :

  • parallel: N = N copies identiques du job (utile pour partitionner des tests)
  • parallel:matrix = copies différenciées par variables (Node 22, 24, 26...)

Ce guide couvre parallel:matrix. Pour parallel: N seul, consultez la doc officielle.

Ce guide se lit par la section qui correspond à votre besoin. Pour des tests multi-versions (Node.js 22, 24, 26, ou Python 3.13 à 3.15), voyez le cas d'usage multi-versions. Pour des tests multi-OS en parallèle sous Linux, macOS et Windows, lisez la matrice multi-OS. Pour des combinaisons complexes (version, OS et base de données), allez aux matrices multidimensionnelles. Enfin, pour choisir entre matrice et pipelines dynamiques, la section matrix vs pipelines dynamiques compare les deux.

À la fin de ce module, vous saurez :

  • Configurer parallel:matrix : syntaxe et options
  • Tester multi-versions : Node.js, Python, Go en parallèle
  • Tester multi-OS : Linux, macOS, Windows avec des tags
  • Créer des matrices multidimensionnelles : version × OS × database
  • Combiner avec needs : dépendances entre jobs matriciels
  • Choisir matrix vs dynamique : quand utiliser chaque approche

Avant de continuer, assurez-vous de maîtriser :

Sans parallel:matrix, pour tester trois versions de Node.js, vous devriez écrire :

# ❌ Sans matrix : 3 jobs quasi-identiques
test_node22:
image: node:22
script: npm test
test_node24:
image: node:24
script: npm test
test_node26:
image: node:26
script: npm test

Trois jobs avec le même script, seule l'image change. Si vous modifiez le script, vous devez penser à le faire trois fois. Si vous ajoutez une quatrième version, vous créez un quatrième job. C'est fragile et fastidieux.

parallel:matrix résout ce problème en créant plusieurs instances d'un même job avec des valeurs de variables différentes. C'est l'équivalent d'une boucle for sur vos jobs : "pour chaque version dans [22, 24, 26], exécute ce job".

Le schéma montre ce dépliage : un seul job template en haut, et en dessous les trois jobs que GitLab génère, chacun avec sa propre valeur de variable (NODE_VERSION).

Principe de parallel:matrix : le job template test avec matrix 22, 24, 26 génère trois jobs test: [22], test: [24] et test: [26], chacun avec sa variable NODE_VERSION.GitLab génèreGitLab génèreGitLab génèreJob template test:matrix: NODE_VERSION [22, 24, 26]test: [22]NODE_VERSION=22test: [24]NODE_VERSION=24test: [26]NODE_VERSION=26
Les trois jobs générés s'exécutent en parallèle. Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Concrètement, GitLab prend votre job "template" et le duplique autant de fois qu'il y a de valeurs dans votre matrice, en injectant la variable correspondante dans chaque copie.

Voici la structure d'un job avec parallel:matrix. Chaque ligne a un rôle précis :

stages:
- test
test:
stage: test
image: node:$NODE_VERSION # 1️⃣ La variable sera remplacée
parallel: # 2️⃣ Active le mode parallèle
matrix: # 3️⃣ Définit une matrice de valeurs
- NODE_VERSION: ["22", "24", "26"] # 4️⃣ Liste des valeurs à tester
script:
- node --version # Affiche la version (utile pour debug)
- npm ci # Installe les dépendances
- npm test # Lance les tests

Décortiquons chaque élément :

  1. image: node:$NODE_VERSION, Le $ indique une variable. GitLab la remplacera par 22, 24 ou 26 selon l'instance du job.

  2. parallel:, Ce mot-clé active l'exécution parallèle. Sans lui, vous n'avez qu'un seul job.

  3. matrix:, Indique que vous voulez générer des jobs à partir d'une combinaison de variables (et non juste N copies identiques).

  4. NODE_VERSION: ["22", "24", "26"], La liste des valeurs. Chaque élément génère un job distinct.

À partir de ce seul job, GitLab crée 3 jobs distincts qui s'exécutent en parallèle :

Job crééVariable injectéeImage Docker utilisée
test: [22]NODE_VERSION=22node:22
test: [24]NODE_VERSION=24node:24
test: [26]NODE_VERSION=26node:26

Les trois jobs démarrent en même temps (si des runners sont disponibles), ce qui divise le temps d'exécution par trois.

GitLab injecte automatiquement des variables dans chaque job matriciel. Ces variables sont utiles pour le debug, les logs, ou pour adapter le comportement selon la position dans la matrice.

VariableÀ quoi ça sertValeur pour le job test: [24]
CI_NODE_INDEXSavoir quel job on est (1er, 2ème, 3ème...)2
CI_NODE_TOTALConnaître le nombre total de jobs générés3
NODE_VERSIONVotre variable de matrice24

Exemple concret : afficher la progression dans les logs.

test:
parallel:
matrix:
- NODE_VERSION: ["22", "24", "26"]
script:
- echo "🔄 Job $CI_NODE_INDEX / $CI_NODE_TOTAL" # Affiche "Job 2 / 3"
- echo "📦 Testing Node.js $NODE_VERSION" # Affiche "Testing Node.js 24"
- npm test

Le cas d'usage le plus courant de parallel:matrix est la validation de compatibilité : vous voulez garantir que votre code fonctionne sur plusieurs versions d'un runtime (Node.js, Python, Go...) sans maintenir un job par version.

Pour une application Node.js, testez les lignes LTS maintenues et la ligne Current. En octobre 2026, le calendrier officiel de Node.js donne la 22 en maintenance LTS, la 24 en LTS active et la 26 en Current, qui passe LTS le 28 octobre 2026. L'image Docker est paramétrée dynamiquement avec $NODE_VERSION, et GitLab génère un rapport JUnit par version pour visualiser les résultats dans l'interface.

test_nodejs:
stage: test
image: node:$NODE_VERSION-alpine
parallel:
matrix:
- NODE_VERSION: ["22", "24", "26"]
script:
- npm ci
- npm test -- --reporter=junit --outputFile=junit-$NODE_VERSION.xml
artifacts:
reports:
junit: junit-$NODE_VERSION.xml # Fichier unique par version

Avec cette configuration, si Node 26 introduit une régression dans une dépendance, vous le détectez immédiatement sans impacter les autres versions.

Pour Python, la matrice teste les versions mineures supportées. Notez l'utilisation de before_script pour l'installation des dépendances, ce bloc s'exécute avant chaque instance de la matrice.

test_python:
stage: test
image: python:$PYTHON_VERSION
parallel:
matrix:
- PYTHON_VERSION: ["3.13", "3.14", "3.15"]
before_script:
- pip install -r requirements.txt
script:
- pytest --junitxml=report.xml
artifacts:
reports:
junit: report.xml

Chaque version Python peut avoir des comportements différents (f-strings, match/case, typing). La matrice garantit une compatibilité réelle, pas supposée.

Go suit un cycle de release prévisible : une version majeure tous les six mois, et chacune reste supportée jusqu'à la sortie des deux suivantes (politique de release de Go). En octobre 2026, ce sont 1.26 et 1.27. La matrice couvre ces deux versions ; une release candidate de la prochaine (tag du type 1.28rc1) peut s'y ajouter pendant les semaines qui précèdent sa sortie.

test_go:
stage: test
image: golang:$GO_VERSION
parallel:
matrix:
- GO_VERSION: ["1.26", "1.27"]
script:
- go test -v ./...

Le compilateur Go étant strict, cette matrice détecte rapidement les incompatibilités liées aux génériques ou aux nouvelles API de la bibliothèque standard.

Certaines applications doivent fonctionner sur plusieurs systèmes d'exploitation : une CLI en Go, un outil de build, une application Electron... Plutôt que de tester manuellement sur chaque OS, la matrice automatise cette validation.

Le défi : chaque OS nécessite un runner différent

Section intitulée « Le défi : chaque OS nécessite un runner différent »

Contrairement aux versions de Node (où seule l'image Docker change), tester sur macOS ou Windows nécessite un runner physique ou virtuel tournant sur cet OS. On utilise les tags pour diriger chaque job vers le bon runner.

test_multiplatform:
stage: test
parallel:
matrix:
# Chaque entrée définit l'OS ET le tag du runner correspondant
- PLATFORM: linux
RUNNER_TAG: linux
- PLATFORM: macos
RUNNER_TAG: macos
- PLATFORM: windows
RUNNER_TAG: windows
tags:
- $RUNNER_TAG # Le job sera assigné au runner avec ce tag
script:
- echo "Testing on $PLATFORM"
- ./run-tests.sh # Le même script, exécuté sur chaque OS

Comment ça marche :

  1. GitLab génère 3 jobs : test_multiplatform: [linux], test_multiplatform: [macos], test_multiplatform: [windows]
  2. Chaque job a un tag différent (linux, macos, windows)
  3. GitLab assigne chaque job au runner qui porte le tag correspondant
  4. Les trois tests s'exécutent en parallèle, chacun sur son OS natif

Jusqu'ici, nos matrices n'avaient qu'une seule variable (la version de Node). Mais que faire si vous voulez tester plusieurs dimensions en même temps ? Par exemple : Node 22 ET 24, avec PostgreSQL ET MySQL ?

C'est là que les matrices deviennent puissantes : GitLab génère automatiquement toutes les combinaisons possibles.

test:
stage: test
image: node:$NODE_VERSION
services:
- name: $DATABASE
alias: db
parallel:
matrix:
- NODE_VERSION: ["22", "24"]
DATABASE: ["postgres:15", "postgres:16", "mysql:8"]
script:
- npm ci
- npm test

Cet exemple génère 6 jobs (2 versions × 3 bases de données) :

  • test: [22, postgres:15]
  • test: [22, postgres:16]
  • test: [22, mysql:8]
  • test: [24, postgres:15]
  • test: [24, postgres:16]
  • test: [24, mysql:8]
build:
stage: build
parallel:
matrix:
- GOOS: [linux, darwin, windows]
GOARCH: [amd64, arm64]
GO_VERSION: ["1.27"]
image: golang:$GO_VERSION
script:
- GOOS=$GOOS GOARCH=$GOARCH go build -o app-$GOOS-$GOARCH ./...
artifacts:
paths:
- app-*

Résultat : 6 binaires compilés en parallèle.

Parfois, vous ne voulez pas toutes les combinaisons. Vous avez des configurations spécifiques qui vont ensemble, et mélanger les variables n'aurait pas de sens.

Exemple concret : vous déployez sur trois environnements, chacun avec son cluster Kubernetes et son nombre de réplicas. Ce n'est pas "staging × prod × 3 clusters" (9 combinaisons), c'est "staging-eu, prod-eu, prod-us" (3 configurations distinctes).

Pour cela, définissez chaque configuration sur sa propre ligne dans la matrice :

deploy:
stage: deploy
parallel:
matrix:
# Chaque ligne = une configuration complète
- ENVIRONMENT: staging
CLUSTER: staging-eu
REPLICAS: "2"
- ENVIRONMENT: production
CLUSTER: prod-eu
REPLICAS: "5"
- ENVIRONMENT: production
CLUSTER: prod-us
REPLICAS: "3"
script:
- kubectl config use-context $CLUSTER
- kubectl scale deployment app --replicas=$REPLICAS
environment:
name: $ENVIRONMENT

Ici, 3 jobs (pas 9), car chaque entrée définit une configuration complète.

Le mot-clé needs (que vous connaissez peut-être du guide DAG) permet de définir des dépendances entre jobs. Avec les matrices, une question se pose : si test génère 3 jobs, le job deploy doit-il attendre les 3, ou un seul ?

Par défaut, needs: ["test"] attend toutes les instances générées par la matrice. C'est le comportement le plus courant : on ne déploie que si tous les tests passent.

test:
stage: test
parallel:
matrix:
- NODE_VERSION: ["22", "24", "26"]
script:
- npm test
deploy:
stage: deploy
needs: ["test"] # ⏳ Attend les 3 jobs : test:[22], test:[24], test:[26]
script:
- ./deploy.sh

Comportement : deploy ne démarre que lorsque test: [22], test: [24] ET test: [26] sont tous au vert.

Parfois, vous ne voulez pas attendre toutes les combinaisons. Par exemple, vous buildez pour 6 plateformes (3 OS × 2 architectures), mais le déploiement sur un serveur Linux n'a besoin que du binaire linux/amd64.

C'est là qu'intervient needs:parallel:matrix : il permet de cibler une combinaison précise au lieu de toute la matrice.

build:
stage: build
parallel:
matrix:
- PLATFORM: [linux, macos, windows]
ARCH: [amd64, arm64]
script:
- ./build.sh # Compile pour $PLATFORM / $ARCH
# 🎯 Déployer DÈS QUE le build linux/amd64 est prêt
# (sans attendre macos, windows, arm64...)
deploy_linux:
stage: deploy
needs:
- job: build # Le nom du job source
parallel:
matrix:
- PLATFORM: linux # Cible cette combinaison précise
ARCH: amd64
script:
- ./deploy.sh linux-amd64

Avantage : deploy_linux démarre dès que build: [linux, amd64] est terminé, même si les builds macOS ou arm64 sont encore en cours.

GitLab supporte des expressions pour cibler plusieurs combinaisons d'une matrice dans needs:parallel:matrix :

build:
parallel:
matrix:
- OS: [linux, macos, windows]
ARCH: [amd64, arm64]
# Attendre tous les builds Linux (amd64 et arm64)
package_linux:
needs:
- job: build
parallel:
matrix:
- OS: linux
ARCH: [amd64, arm64] # Correspond aux deux combinaisons Linux

Cette syntaxe évite de lister chaque combinaison individuellement.

Vous voulez peut-être adapter le comportement de la matrice selon le contexte. Par exemple :

  • Sur une merge request : tester uniquement la version LTS (rapide, feedback immédiat)
  • Sur la branche main : tester toutes les versions (validation complète avant release)

Méthode 1 : exclure des combinaisons avec when: never

Section intitulée « Méthode 1 : exclure des combinaisons avec when: never »

La matrice est dépliée d'abord, puis chaque job passe dans rules. Utilisez when: never pour exclure certaines combinaisons :

test:
stage: test
image: node:$NODE_VERSION
parallel:
matrix:
- NODE_VERSION: ["22", "24", "26"]
rules:
# 📝 En MR : exclure tout sauf Node 24
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $NODE_VERSION != "24"'
when: never
# 🚀 Sinon : exécuter normalement
- when: on_success
script:
- npm test

Résultat :

  • MR → 1 job (test: [24]) → feedback rapide
  • main → 3 jobs parallèles → validation complète

GitLab propose deux façons de générer plusieurs jobs à partir d'un seul fichier .gitlab-ci.yml. Comment choisir ?

Critèreparallel:matrixPipeline dynamique
ComplexitéSimple, déclaratifPlus complexe (génération YAML)
FlexibilitéMême job, variables différentesJobs totalement différents
DebugFacile (tout est visible dans le YAML)Plus difficile (YAML généré à la volée)
CombinaisonsConnues à l'avance (statiques)Détectées au runtime (dynamiques)
Cas d'usage typiqueTests multi-versions, build multi-OSMonorepo, jobs selon fichiers modifiés
Limite200 combinaisons max par matriceLimites générales des pipelines de l'instance

Utilisez parallel:matrix quand vos combinaisons sont prévisibles et stables. Vous connaissez à l'avance les versions à tester, et le script reste identique, seules les variables changent.

Cas typiques :

  • Tests multi-versions : Node 22/24/26, Python 3.13/3.14/3.15. Les versions évoluent lentement (tous les 6-12 mois).
  • Build multi-plateforme : Linux/macOS/Windows × amd64/arm64. L'ensemble des cibles est fixe.
  • Validation de compatibilité : tester avec PostgreSQL ET MySQL, pas l'un ou l'autre.

Le point commun : même logique, contextes différents.

Préférez les pipelines dynamiques quand les jobs à exécuter dépendent du contenu du commit ou nécessitent une logique de génération.

Cas typiques :

  • Monorepo : détecter quels packages ont changé et ne tester que ceux-là.
  • Jobs conditionnels complexes : générer un job de déploiement uniquement si certains fichiers sont modifiés.
  • Scripts différents par contexte : le job "build" d'un service Go n'a rien à voir avec celui d'un frontend React.

Le point commun : logique variable, décision au runtime.

Chaque variable multiplie le nombre de jobs. Avec 3 dimensions de 3 valeurs chacune, vous obtenez 27 jobs, et vous approchez vite de la limite de 200. La solution : identifier les combinaisons réellement critiques plutôt que tester exhaustivement.

Dans l'exemple ci-dessous, on teste toutes les bases de données avec la version LTS (Node 24), mais on ne valide qu'une seule DB pour les versions secondaires. Résultat : 5 jobs au lieu de 27, avec une couverture suffisante.

# ❌ 3 × 3 × 3 = 27 jobs !
test:
parallel:
matrix:
- NODE: [22, 24, 26]
DB: [postgres, mysql, sqlite]
CACHE: [redis, memcached, none]
# ✅ Tester les combinaisons critiques seulement
test:
parallel:
matrix:
- NODE: "24"
DB: [postgres, mysql, sqlite]
- NODE: [22, 26]
DB: postgres # Un seul DB pour les autres versions

Quand un job matriciel produit des artefacts, nommez-les avec les variables de la matrice. Sans cela, les fichiers se chevauchent ou deviennent impossibles à identifier dans l'interface GitLab.

L'attribut name définit le nom du ZIP téléchargeable, et les variables sont interpolées automatiquement.

build:
parallel:
matrix:
- OS: [linux, windows]
ARCH: [amd64, arm64]
script:
- ./build.sh
artifacts:
paths:
- build/app-$OS-$ARCH*
name: "build-$OS-$ARCH"

Par défaut, si un job de la matrice échoue, tout le pipeline échoue. C'est souvent le comportement souhaité, mais parfois vous voulez continuer malgré un échec sur une version expérimentale (ex : une ligne Current de Node.js avant son passage en LTS, comme la 26 jusqu'au 28 octobre 2026).

Utilisez allow_failure avec des codes de sortie spécifiques pour distinguer les vrais échecs des avertissements.

test:
parallel:
matrix:
- NODE_VERSION: ["22", "24", "26"]
script:
- npm test
allow_failure:
exit_codes: [42] # Code spécifique = warning, pas blocage

Dans l'interface GitLab, les jobs matriciels apparaissent comme test: [22], test: [24]... C'est suffisant pour des matrices simples, mais avec plusieurs dimensions, ça devient illisible (test: [22, postgres, redis]).

Ajoutez une variable descriptive et affichez-la en début de script pour faciliter le diagnostic.

test:
parallel:
matrix:
- NODE_VERSION: ["22", "24", "26"]
variables:
JOB_DESCRIPTION: "Node.js $NODE_VERSION tests"
script:
- echo "$JOB_DESCRIPTION"
- npm test

Voici les problèmes les plus courants et comment les résoudre :

Symptôme : Le pipeline échoue immédiatement avec ce message.

Cause : Votre matrice génère plus de 200 jobs (la limite GitLab).

Exemple : 4 versions × 5 DB × 3 OS × 4 navigateurs = 240 jobs → échec.

Solution : Réduisez les combinaisons (voir section Limiter l'explosion combinatoire).

Symptôme : "Job not found" ou dépendance cassée.

Cause : L'ordre des variables dans needs:parallel:matrix ne correspond pas à celui du job source.

Solution : Vérifiez que vous utilisez exactement le même ordre :

# Si le job source est :
build:
parallel:
matrix:
- OS: [linux, windows] # OS en premier
ARCH: [amd64, arm64] # ARCH en second
# Le needs doit respecter cet ordre :
needs:
- job: build
parallel:
matrix:
- OS: linux # OS en premier
ARCH: amd64 # ARCH en second

Symptôme : le job échoue avant le script, au téléchargement de l'image, sur un nom incomplet ou invalide au lieu de node:22.

Cause : la variable n'existe pas au moment où le runner prépare l'image. Les guillemets YAML ne sont pas en cause : node:$NODE_VERSION, "node:$NODE_VERSION" et 'node:$NODE_VERSION' donnent la même chaîne une fois le YAML lu, et GitLab l'interpole de la même façon. La documentation GitLab précise que le champ image est développé par le runner : il connaît les variables de projet et de groupe, celles du .gitlab-ci.yml (dont la matrice) et celles des déclenchements, mais pas une variable définie dans le script (export NODE_VERSION=22).

Solution : vérifiez que le nom de la variable est identique, casse comprise, dans image: et dans la matrice, et ne comptez jamais sur un export du script pour choisir l'image :

# ✅ Trois écritures équivalentes (une seule par job)
image: node:$NODE_VERSION
# image: "node:$NODE_VERSION"
# image: 'node:$NODE_VERSION'
# ❌ Faute de frappe : NODE_VERSIONS n'existe pas dans la matrice
image: node:$NODE_VERSIONS

Symptôme : Aucun job n'est généré.

Cause : Vous avez un tableau vide [] ou aucune valeur.

Solution : Chaque variable doit avoir au moins une valeur :

# ❌ Tableau vide = aucun job
parallel:
matrix:
- NODE_VERSION: [] # Erreur !
# ✅ Au moins une valeur
parallel:
matrix:
- NODE_VERSION: ["24"] # OK, 1 job

GitLab nomme automatiquement les jobs matriciels selon le pattern job: [val1, val2]. Ce nommage apparaît dans l'interface et les logs, autant qu'il soit lisible.

À éviterPréférerPourquoi
node:22-alpine22Valeurs courtes = lisibilité
postgres:15-alpinepg15Alias courts pour les services
us-east-1us-e1Abréviations reconnues

Exemple concret :

# ❌ Illisible dans l'UI : test: [node:22-alpine, postgres:15-alpine]
parallel:
matrix:
- IMAGE: ["node:22-alpine", "node:24-alpine"]
DB: ["postgres:15-alpine", "mysql:8"]
# ✅ Lisible : test: [22, pg15]
parallel:
matrix:
- NODE: ["22", "24"]
DB: ["pg15", "mysql8"]
variables:
NODE_IMAGE: "node:$NODE-alpine"
DB_IMAGE: "$DB" # Mapping dans le script ou variables

Utilisez des alias courts dans la matrice, et construisez les vraies valeurs (images Docker, URLs) dans variables: ou le script.

  1. parallel:matrix multiplie un job avec différentes valeurs de variables
  2. Nommage automatique : job: [val1, val2]
  3. Variables disponibles : CI_NODE_INDEX, CI_NODE_TOTAL, vos variables
  4. Combinatoire : toutes les permutations sont générées
  5. needs: ["job"] attend toutes les instances matricielles
  6. needs:parallel:matrix pour cibler une combinaison spécifique
  7. Limite : 200 jobs maximum par matrice
  8. Alternative : pipelines dynamiques pour des cas plus complexes

Appliquez ces patterns dans le Lab 15, Matrices multi-versions.

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 projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn