
Vous devez tester sur Node 18, 20 et 22 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.
Ce guide est fait pour vous si…
Section intitulée « Ce guide est fait pour vous si… »Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »À 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
Prérequis
Section intitulée « Prérequis »Avant de continuer, assurez-vous de maîtriser :
Principe de parallel:matrix
Section intitulée « Principe de parallel:matrix »Le problème : la duplication de code
Section intitulée « Le problème : la duplication de code »Sans parallel:matrix, pour tester trois versions de Node.js, vous devriez écrire :
# ❌ Sans matrix : 3 jobs quasi-identiquestest_node18: image: node:18 script: npm test
test_node20: image: node:20 script: npm test
test_node22: image: node:22 script: npm testTrois 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 Node 24, vous créez un quatrième job. C’est fragile et fastidieux.
La solution : une matrice de variables
Section intitulée « La solution : une matrice de variables »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 [18, 20, 22], exécute ce job”.
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.
Configuration de base
Section intitulée « Configuration de base »Syntaxe minimale
Section intitulée « Syntaxe minimale »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: ["18", "20", "22"] # 4️⃣ Liste des valeurs à tester script: - node --version # Affiche la version (utile pour debug) - npm ci # Installe les dépendances - npm test # Lance les testsDécortiquons chaque élément :
-
image: node:$NODE_VERSION— Le$indique une variable. GitLab la remplacera par18,20ou22selon l’instance du job. -
parallel:— Ce mot-clé active l’exécution parallèle. Sans lui, vous n’avez qu’un seul job. -
matrix:— Indique que vous voulez générer des jobs à partir d’une combinaison de variables (et non juste N copies identiques). -
NODE_VERSION: ["18", "20", "22"]— La liste des valeurs. Chaque élément génère un job distinct.
Ce que GitLab génère
Section intitulée « Ce que GitLab génère »À partir de ce seul job, GitLab crée 3 jobs distincts qui s’exécutent en parallèle :
| Job créé | Variable injectée | Image Docker utilisée |
|---|---|---|
test: [18] | NODE_VERSION=18 | node:18 |
test: [20] | NODE_VERSION=20 | node:20 |
test: [22] | NODE_VERSION=22 | node:22 |
Les trois jobs démarrent en même temps (si des runners sont disponibles), ce qui divise le temps d’exécution par trois.
Variables prédéfinies
Section intitulée « Variables prédéfinies »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 sert | Valeur pour le job test: [20] |
|---|---|---|
CI_NODE_INDEX | Savoir quel job on est (1er, 2ème, 3ème…) | 2 |
CI_NODE_TOTAL | Connaître le nombre total de jobs générés | 3 |
NODE_VERSION | Votre variable de matrice | 20 |
Exemple concret : afficher la progression dans les logs.
test: parallel: matrix: - NODE_VERSION: ["18", "20", "22"] script: - echo "🔄 Job $CI_NODE_INDEX / $CI_NODE_TOTAL" # Affiche "Job 2 / 3" - echo "📦 Testing Node.js $NODE_VERSION" # Affiche "Testing Node.js 20" - npm testCas d’usage : tests multi-versions
Section intitulée « Cas d’usage : tests multi-versions »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.
Tests Node.js
Section intitulée « Tests Node.js »Pour une application Node.js, testez les versions LTS actives. 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: ["18", "20", "22"] script: - npm ci - npm test -- --reporter=junit --outputFile=junit-$NODE_VERSION.xml artifacts: reports: junit: junit-$NODE_VERSION.xml # Fichier unique par versionAvec cette configuration, si Node 22 introduit une régression dans une dépendance, vous le détectez immédiatement sans impacter les autres versions.
Tests Python
Section intitulée « Tests Python »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.10", "3.11", "3.12"] before_script: - pip install -r requirements.txt script: - pytest --junitxml=report.xml artifacts: reports: junit: report.xmlChaque version Python peut avoir des comportements différents (f-strings, match/case, typing). La matrice garantit une compatibilité réelle, pas supposée.
Tests Go
Section intitulée « Tests Go »Go suit un cycle de release prévisible avec deux versions supportées. La matrice couvre la version actuelle, la précédente, et éventuellement la prochaine en RC.
test_go: stage: test image: golang:$GO_VERSION parallel: matrix: - GO_VERSION: ["1.21", "1.22", "1.23"] 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.
Matrice multi-OS
Section intitulée « Matrice multi-OS »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 OSComment ça marche :
- GitLab génère 3 jobs :
test_multiplatform: [linux],test_multiplatform: [macos],test_multiplatform: [windows] - Chaque job a un tag différent (
linux,macos,windows) - GitLab assigne chaque job au runner qui porte le tag correspondant
- Les trois tests s’exécutent en parallèle, chacun sur son OS natif
Matrices multidimensionnelles
Section intitulée « Matrices multidimensionnelles »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 18 ET 20, avec PostgreSQL ET MySQL ?
C’est là que les matrices deviennent puissantes : GitLab génère automatiquement toutes les combinaisons possibles.
Deux dimensions : version × base de données
Section intitulée « Deux dimensions : version × base de données »test: stage: test image: node:$NODE_VERSION services: - name: $DATABASE alias: db parallel: matrix: - NODE_VERSION: ["18", "20"] DATABASE: ["postgres:15", "postgres:16", "mysql:8"] script: - npm ci - npm testCet exemple génère 6 jobs (2 versions × 3 bases de données) :
test: [18, postgres:15]test: [18, postgres:16]test: [18, mysql:8]test: [20, postgres:15]test: [20, postgres:16]test: [20, mysql:8]
Trois dimensions : version × OS × architecture
Section intitulée « Trois dimensions : version × OS × architecture »build: stage: build parallel: matrix: - GOOS: [linux, darwin, windows] GOARCH: [amd64, arm64] GO_VERSION: ["1.22"] 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.
Grouper des configurations (non combinatoires)
Section intitulée « Grouper des configurations (non combinatoires) »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: $ENVIRONMENTIci, 3 jobs (pas 9), car chaque entrée définit une configuration complète.
Utiliser needs avec des matrices
Section intitulée « Utiliser needs avec des matrices »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 ?
Attendre tous les jobs d’une matrice
Section intitulée « Attendre tous les jobs d’une matrice »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: ["18", "20", "22"] script: - npm test
deploy: stage: deploy needs: ["test"] # ⏳ Attend les 3 jobs : test:[18], test:[20], test:[22] script: - ./deploy.shComportement : deploy ne démarre que lorsque test: [18], test: [20] ET test: [22] sont tous au vert.
Attendre un job matriciel spécifique
Section intitulée « Attendre un job matriciel spécifique »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-amd64Avantage : deploy_linux démarre dès que build: [linux, amd64] est terminé, même si les builds macOS ou arm64 sont encore en cours.
Filtrer avec des expressions matricielles
Section intitulée « Filtrer avec des expressions matricielles »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 LinuxCette syntaxe évite de lister chaque combinaison individuellement.
Combiner matrix avec rules
Section intitulée « Combiner matrix avec rules »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: ["18", "20", "22"] rules: # 📝 En MR : exclure tout sauf Node 20 - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $NODE_VERSION != "20"' when: never # 🚀 Sinon : exécuter normalement - when: on_success script: - npm testRésultat :
- MR → 1 job (
test: [20]) → feedback rapide - main → 3 jobs parallèles → validation complète
matrix vs pipelines dynamiques
Section intitulée « matrix vs pipelines dynamiques »GitLab propose deux façons de générer plusieurs jobs à partir d’un seul fichier .gitlab-ci.yml. Comment choisir ?
| Critère | parallel:matrix | Pipeline dynamique |
|---|---|---|
| Complexité | Simple, déclaratif | Plus complexe (génération YAML) |
| Flexibilité | Même job, variables différentes | Jobs totalement différents |
| Debug | Facile (tout est visible dans le YAML) | Plus difficile (YAML généré à la volée) |
| Combinaisons | Connues à l’avance (statiques) | Détectées au runtime (dynamiques) |
| Cas d’usage typique | Tests multi-versions, build multi-OS | Monorepo, jobs selon fichiers modifiés |
| Limite | 200 jobs max par matrice | 150 jobs par child pipeline |
Quand choisir matrix
Section intitulée « Quand choisir matrix »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 18/20/22, Python 3.10/3.11/3.12. 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.
Quand choisir les pipelines dynamiques
Section intitulée « Quand choisir les pipelines dynamiques »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.
Bonnes pratiques
Section intitulée « Bonnes pratiques »1. Limiter l’explosion combinatoire
Section intitulée « 1. Limiter l’explosion combinatoire »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 20), 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: [18, 20, 22] DB: [postgres, mysql, sqlite] CACHE: [redis, memcached, none]
# ✅ Tester les combinaisons critiques seulementtest: parallel: matrix: - NODE: "20" DB: [postgres, mysql, sqlite] - NODE: [18, 22] DB: postgres # Un seul DB pour les autres versions2. Nommer les artefacts par combinaison
Section intitulée « 2. Nommer les artefacts par combinaison »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"3. Gérer les échecs partiels avec allow_failure
Section intitulée « 3. Gérer les échecs partiels avec allow_failure »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 : Node 23 en RC).
Utilisez allow_failure avec des codes de sortie spécifiques pour distinguer les vrais échecs des avertissements.
test: parallel: matrix: - NODE_VERSION: ["18", "20", "22"] script: - npm test allow_failure: exit_codes: [42] # Code spécifique = warning, pas blocage4. Tagger les jobs pour le debug
Section intitulée « 4. Tagger les jobs pour le debug »Dans l’interface GitLab, les jobs matriciels apparaissent comme test: [18], test: [20]… C’est suffisant pour des matrices simples, mais avec plusieurs dimensions, ça devient illisible (test: [18, postgres, redis]).
Ajoutez une variable descriptive et affichez-la en début de script pour faciliter le diagnostic.
test: parallel: matrix: - NODE_VERSION: ["18", "20", "22"] variables: JOB_DESCRIPTION: "Node.js $NODE_VERSION tests" script: - echo "$JOB_DESCRIPTION" - npm testErreurs fréquentes
Section intitulée « Erreurs fréquentes »Voici les problèmes les plus courants et comment les résoudre :
1. “This job could not be created”
Section intitulée « 1. “This job could not be created” »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).
2. Job non trouvé avec needs:parallel:matrix
Section intitulée « 2. Job non trouvé avec needs:parallel:matrix »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 second3. Variables non interpolées dans l’image
Section intitulée « 3. Variables non interpolées dans l’image »Symptôme : GitLab utilise littéralement $NODE_VERSION au lieu de 18.
Cause : Vous avez utilisé des guillemets simples au lieu de doubles.
Solution : Les variables s’interpolèrent correctement avec $VAR directement :
# ✅ Correctimage: node:$NODE_VERSION
# ✅ Correct aussiimage: "node:$NODE_VERSION"
# ❌ Problème potentiel avec guillemets simplesimage: 'node:$NODE_VERSION' # $NODE_VERSION n'est pas remplacé4. Matrice vide
Section intitulée « 4. Matrice vide »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 jobparallel: matrix: - NODE_VERSION: [] # Erreur !
# ✅ Au moins une valeurparallel: matrix: - NODE_VERSION: ["20"] # OK, 1 jobNommage des jobs et ergonomie UI
Section intitulée « Nommage des jobs et ergonomie UI »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.
Conseils pour un affichage clair
Section intitulée « Conseils pour un affichage clair »| À éviter | Préférer | Pourquoi |
|---|---|---|
node:18-alpine | 18 | Valeurs courtes = lisibilité |
postgres:15-alpine | pg15 | Alias courts pour les services |
us-east-1 | us-e1 | Abréviations reconnues |
Exemple concret :
# ❌ Illisible dans l'UI : test: [node:18-alpine, postgres:15-alpine]parallel: matrix: - IMAGE: ["node:18-alpine", "node:20-alpine"] DB: ["postgres:15-alpine", "mysql:8"]
# ✅ Lisible : test: [18, pg15]parallel: matrix: - NODE: ["18", "20"] DB: ["pg15", "mysql8"]variables: NODE_IMAGE: "node:$NODE-alpine" DB_IMAGE: "$DB" # Mapping dans le script ou variablesUtilisez des alias courts dans la matrice, et construisez les vraies valeurs (images Docker, URLs) dans variables: ou le script.
À retenir
Section intitulée « À retenir »parallel:matrixmultiplie un job avec différentes valeurs de variables- Nommage automatique :
job: [val1, val2] - Variables disponibles :
CI_NODE_INDEX,CI_NODE_TOTAL, vos variables - Combinatoire : toutes les permutations sont générées
needs: ["job"]attend toutes les instances matriciellesneeds:parallel:matrixpour cibler une combinaison spécifique- Limite : 200 jobs maximum par matrice
- Alternative : pipelines dynamiques pour des cas plus complexes