Aller au contenu
CI/CD & Automatisation medium

Pipelines parent-enfant GitLab CI/CD

26 min de lecture

logo gitlab

Votre pipeline a 50 jobs et devient ingérable ? Les pipelines parent-enfant permettent de découper un pipeline complexe en sous-pipelines indépendants. Le parent orchestre, les enfants exécutent.

Les pipelines parent-enfant répondent à quatre situations bien distinctes, et vous n'avez probablement pas besoin des quatre. Chaque carte ci-dessous mène directement à la section correspondante : suivez celle qui décrit votre problème plutôt que de lire la page dans l'ordre.

À la fin de ce module, vous saurez :

  • Déclencher un pipeline enfant : trigger: include avec un fichier local
  • Contrôler l'attente : strategy: depend vs strategy: mirror
  • Passer des variables : explicites ou via dotenv
  • Partager des artefacts : needs:pipeline:job entre parent et enfant
  • Déclencher multi-projet : trigger: project vers un autre repo
  • Organiser un monorepo : un pipeline par composant

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

Un pipeline enfant est un pipeline à part entière, avec ses propres stages, ses propres jobs et son propre identifiant, déclenché depuis un autre pipeline. Cette séparation change trois choses : le fichier de configuration se scinde, l'interface GitLab affiche chaque composant isolément, et un échec reste circonscrit à son périmètre. Le prix à payer est une frontière réelle entre les deux pipelines, que ni les variables ni les artefacts ne franchissent tout seuls.

Sans découpage, votre .gitlab-ci.yml ressemble à ça :

# ❌ Un fichier de 500 lignes avec TOUT dedans
build_frontend:
script: npm run build
build_backend:
script: go build
build_api:
script: python setup.py
test_frontend:
script: npm test
test_backend:
script: go test
test_api:
script: pytest
deploy_frontend:
# ... et ça continue

Problèmes concrets :

  • Maintenance : Qui est responsable de quoi ? Tout le monde modifie le même fichier.
  • Lisibilité : 50 jobs dans un seul fichier = cauchemar à débugger.
  • Performance : Le backend n'a pas changé ? Dommage, on le rebuild quand même.
  • Isolation : Le test frontend échoue ? Impossible de déployer le backend qui est OK.

Le découpage suit l'organisation du dépôt : un fichier de configuration par répertoire applicatif, maintenu par l'équipe qui possède ce code. Le fichier racine ne contient plus que les jobs de déclenchement, ce qui le maintient lisible même quand le projet grossit.

Sans parent-enfantAvec parent-enfant
Un seul .gitlab-ci.yml géantFichiers séparés par composant
Difficile à maintenirResponsabilité claire (l'équipe front gère frontend/.gitlab-ci.yml)
Tout s'exécute toujoursExécution conditionnelle par composant
Un échec bloque toutIsolation des problèmes

pipeline parent-enfants

Le montage minimal tient en deux fichiers : un parent qui déclenche, un enfant qui travaille. Aucun des deux ne demande de configuration particulière du côté de GitLab, tout se joue dans le YAML. Le seul mot-clé nouveau à connaître est trigger:.

Un trigger est un job spécial qui ne fait qu'une chose : lancer un autre pipeline. Il ne contient pas de script:, juste une instruction trigger: qui dit "à ce moment, démarre ce pipeline enfant".

Pipeline parent : uniquement des jobs de déclenchement

Section intitulée « Pipeline parent : uniquement des jobs de déclenchement »

Le fichier .gitlab-ci.yml à la racine du projet ne contient plus aucune tâche de build ou de test : il se limite à déclarer quels enfants lancer et à quel moment. Un seul stage suffit dans le cas le plus simple, puisque les jobs de déclenchement s'exécutent en parallèle :

# .gitlab-ci.yml (racine du projet)
stages:
- triggers # Un seul stage : déclencher les enfants
# 🎭 Job qui déclenche le pipeline frontend
trigger_frontend:
stage: triggers
trigger: # 👈 Mot-clé magique : ce n'est pas un job normal
include: frontend/.gitlab-ci.yml # 👈 Chemin vers le fichier CI de l'enfant
strategy: depend # 👈 "Attends que l'enfant finisse"
# 🎭 Job qui déclenche le pipeline backend
trigger_backend:
stage: triggers
trigger:
include: backend/.gitlab-ci.yml
strategy: depend

Décortiquons :

  1. trigger:, Ce mot-clé transforme le job en "lanceur de pipeline". Pas de script:, pas de image:.
  2. include: frontend/.gitlab-ci.yml, Le chemin du fichier CI enfant, relatif à la racine du repo.
  3. strategy: depend, Le parent attend la fin de l'enfant avant de continuer. Sans ça, il passe immédiatement au job suivant.

Le fichier enfant est un .gitlab-ci.yml classique. Il ne sait même pas qu'il est "enfant", il s'exécute normalement :

frontend/.gitlab-ci.yml
stages:
- build
- test
build:
stage: build
image: node:20 # Son propre environnement
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
test:
stage: test
image: node:20@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5
script:
- npm test

Avantage : L'équipe frontend peut modifier ce fichier sans toucher au reste. Chaque composant a son propre "mini-pipeline".

Trois options suffisent à couvrir la quasi-totalité des besoins. include désigne le fichier de l'enfant, strategy décide si le parent attend son résultat, forward contrôle la transmission des variables. La deuxième est celle qu'on oublie le plus souvent, avec les conséquences les plus fâcheuses.

La forme la plus simple, l'enfant est un fichier dans le même repo :

trigger_job:
trigger:
include: path/to/child.yml # Chemin relatif depuis la racine du repo

Sans strategy: depend, voici ce qui se passe :

Parent : trigger_frontend → ✅ success (immédiatement !)
Enfant : → build → test → ❌ échoue
Résultat : le parent est VERT alors que l'enfant a échoué !

Avec strategy: depend :

Parent : trigger_frontend → ⌛ attend...
Enfant : → build → test → ❌ échoue
Résultat : le parent passe en ❌ échec aussi
trigger_job:
trigger:
include: child.yml
strategy: depend # ✅ Attend la fin et hérite du statut

strategy: depend vs strategy: mirror, quelle différence ?

Section intitulée « strategy: depend vs strategy: mirror, quelle différence ? »

Les deux attendent l'enfant, mais diffèrent sur les cas limites :

Situationdependmirror
Enfant réussitParent ✅Parent ✅
Enfant échoueParent ❌Parent ❌
Enfant annulé (cancel)Parent ❌ (failed)Parent ⏹ (cancelled)
Enfant en warningParent ✅Parent ⚠️ (warning)

En pratique :

  • depend : le plus courant, convient à 99% des cas
  • mirror : reflet exact du statut enfant (utile avec les pipelines auto-cancelés)
trigger_job:
trigger:
include: child.yml
strategy: mirror # Miroir strict du statut enfant

forward : transmettre automatiquement les variables

Section intitulée « forward : transmettre automatiquement les variables »

Par défaut, l'enfant ne reçoit pas les variables du parent. Avec forward:, vous pouvez transmettre automatiquement :

trigger_job:
trigger:
include: child.yml
forward:
yaml_variables: true # Variables définies dans le YAML parent
pipeline_variables: true # Variables passées via l'interface web ou API

Quand utiliser forward: ?

  • Vous avez beaucoup de variables à passer
  • Vous voulez que l'enfant hérite du contexte complet du parent

Quand utiliser variables: explicites ? (voir section suivante)

  • Vous voulez contrôler précisément ce qui est passé
  • Vous avez besoin de renommer ou transformer des variables

Deux méthodes coexistent et répondent à des moments différents. La première déclare les valeurs dans le job de déclenchement, elle convient quand elles sont connues au moment d'écrire le YAML. La seconde s'appuie sur un rapport dotenv, un fichier de paires clé-valeur produit par un job précédent, pour transmettre une information calculée pendant l'exécution.

Un pipeline enfant s'exécute dans son propre contexte. Il ne voit pas automatiquement les variables du parent. Si le parent connaît la version à déployer (VERSION=1.2.3), comment la transmettre ?

Définissez les variables directement dans le job trigger :

trigger_frontend:
variables:
ENVIRONMENT: "staging" # Valeur fixe
VERSION: $CI_COMMIT_SHA # Valeur dynamique du parent
DEBUG: "true" # Configuration pour l'enfant
trigger:
include: frontend/.gitlab-ci.yml
strategy: depend

Dans l'enfant, ces variables sont disponibles comme n'importe quelle variable :

frontend/.gitlab-ci.yml
deploy:
script:
- echo "Déploiement version $VERSION sur $ENVIRONMENT"
- ./deploy.sh

Parfois, la valeur à passer n'est connue qu'après l'exécution d'un job. Par exemple : la version est calculée dynamiquement, ou un ID est généré.

Étape 1 : Générer le fichier dotenv dans un job du parent

prepare:
stage: prepare
script:
# Calcule la version dynamiquement
- VERSION=$(./scripts/compute-version.sh)
# Écrit dans le fichier dotenv (format : CLÉ=valeur)
- echo "VERSION=$VERSION" >> build.env
- echo "BUILD_DATE=$(date +%Y-%m-%d)" >> build.env
artifacts:
reports:
dotenv: build.env # 👈 GitLab charge ces variables automatiquement

Étape 2 : Utiliser ces variables dans le trigger

trigger_deploy:
stage: deploy
needs: ["prepare"] # Important : doit attendre le job qui crée le dotenv
variables:
VERSION: $VERSION # Provient de build.env
BUILD_DATE: $BUILD_DATE
trigger:
include: deploy.yml
strategy: depend

Les artefacts sont les fichiers qu'un job conserve pour les jobs suivants. Leur transmission entre un parent et son enfant demande une syntaxe particulière, car les deux pipelines portent des identifiants différents. La méthode se résume à deux gestes : le parent communique son identifiant de pipeline, l'enfant s'en sert pour aller chercher les fichiers.

La déclaration needs habituelle résout les noms de jobs dans le pipeline courant uniquement : y placer le nom d'un job du parent produit une erreur de configuration, pas un téléchargement.

Pourquoi ? Parent et enfant sont des pipelines distincts, avec leurs propres IDs. Le mot-clé needs: ["job"] cherche un job dans le même pipeline.

GitLab fournit une syntaxe spéciale pour récupérer des artefacts entre pipelines.

Parent → Child (même projet) : needs:pipeline:job

Section intitulée « Parent → Child (même projet) : needs:pipeline:job »

L'identifiant du pipeline parent n'est connu qu'à l'exécution : il transite donc comme une variable ordinaire, que l'enfant réutilise ensuite dans sa déclaration needs.

Étape 1 : Le parent produit un artefact ET passe son ID de pipeline

# Dans le parent
build_artifacts:
stage: build
script:
- echo "artifact from parent" > artifact.txt
- ./compile.sh
artifacts:
paths:
- artifact.txt
- dist/
trigger_child:
stage: deploy
trigger:
include: path/to/child-pipeline.yml
strategy: depend
variables:
PARENT_PIPELINE_ID: $CI_PIPELINE_ID # 👈 Passe l'ID du pipeline parent

Étape 2 : L'enfant récupère l'artefact en référençant le pipeline parent

# Dans l'enfant (child-pipeline.yml)
test_child:
stage: test
script:
- ls -la # Vérifie que l'artefact est là
- cat artifact.txt # Utilise l'artefact
needs:
- pipeline: $PARENT_PIPELINE_ID # 👈 ID du pipeline source
job: build_artifacts # 👈 Nom du job qui a créé l'artefact

Comment ça marche :

  1. $CI_PIPELINE_ID contient l'ID unique du pipeline parent (ex: 123456)
  2. On passe cet ID à l'enfant via variables:
  3. L'enfant utilise needs:pipeline: pour dire "va chercher les artefacts de ce pipeline-là"
  4. GitLab télécharge l'artefact du job build_artifacts du pipeline 123456

Pour récupérer un artefact d'un autre projet (pas juste un autre pipeline), utilisez needs:project :

# Dans le pipeline du projet B
test_downstream:
stage: test
script:
- ls -la artifact.txt
- ./use-artifact.sh
needs:
- project: my-group/upstream_project # Chemin du projet source
job: build_artifacts # Job qui a produit l'artefact
ref: main # Branche du projet source
artifacts: true # Télécharger les artefacts

Le mot-clé trigger: sait aussi viser un dépôt distinct : on parle alors de pipeline multi-projet et non de pipeline enfant. La différence n'est pas seulement de vocabulaire. Le pipeline déclenché tourne dans son propre projet, avec sa configuration, ses runners et ses variables, et l'opération met en jeu des autorisations d'accès entre les deux dépôts.

Votre application est découpée en plusieurs repos :

  • my-company/frontend, l'application React
  • my-company/backend, l'API Go
  • my-company/deployment, les scripts de déploiement Kubernetes

Quand le frontend est buildé, vous voulez déclencher le déploiement dans le repo deployment.

Le chemin attendu est celui du dépôt, groupe compris, tel qu'il apparaît dans l'URL du projet. La branche est obligatoire dès que la cible n'est pas la branche par défaut, et c'est le fichier de configuration de cette branche qui sera exécuté.

trigger_deployment:
trigger:
project: my-company/deployment # Chemin complet du projet cible
branch: main # Branche à déclencher
strategy: depend # Attend la fin

Différence avec include: :

  • include: → fichier dans le même repo → child pipeline
  • project: → pipeline dans un autre repo → multi-project pipeline

Passez du contexte au pipeline cible :

deploy_production:
variables:
DEPLOY_ENV: "production" # Où déployer
APP_VERSION: $CI_COMMIT_TAG # Quelle version
SOURCE_PROJECT: $CI_PROJECT_PATH # D'où ça vient
trigger:
project: my-company/deployment
branch: main
strategy: depend

Cet exemple assemble tout ce qui précède sur un cas réel : un dépôt unique contenant deux applications, dont on ne veut reconstruire que la partie modifiée. La détection des changements se fait dans un premier job, qui écrit son verdict dans un rapport dotenv, et les jobs de déclenchement s'appuient sur ces variables dans leurs rules:. Un composant intact ne déclenche aucun pipeline enfant.

Structure :

monorepo/
├── .gitlab-ci.yml # Parent
├── frontend/
│ ├── .gitlab-ci.yml # Enfant frontend
│ └── src/
├── backend/
│ ├── .gitlab-ci.yml # Enfant backend
│ └── src/
└── shared/
└── utils/

Le fichier racine se lit de haut en bas comme une chaîne de décision : un job changes détermine d'abord ce qui a bougé, puis chaque job de déclenchement porte sa propre condition rules: pour ne lancer que le pipeline enfant concerné.

.gitlab-ci.yml
stages:
- prepare
- build
- deploy
# Déterminer ce qui a changé
changes:
stage: prepare
script:
- |
if git diff --name-only HEAD~1 | grep -q "^frontend/"; then
echo "FRONTEND_CHANGED=true" >> build.env
fi
if git diff --name-only HEAD~1 | grep -q "^backend/"; then
echo "BACKEND_CHANGED=true" >> build.env
fi
artifacts:
reports:
dotenv: build.env
# Déclencher frontend si modifié
trigger_frontend:
stage: build
needs: ["changes"]
trigger:
include: frontend/.gitlab-ci.yml
strategy: depend
rules:
- if: $FRONTEND_CHANGED == "true"
# Déclencher backend si modifié
trigger_backend:
stage: build
needs: ["changes"]
trigger:
include: backend/.gitlab-ci.yml
strategy: depend
rules:
- if: $BACKEND_CHANGED == "true"

Rien ne distingue ce fichier d'un pipeline autonome : mêmes stages, même cache, mêmes artefacts. C'est précisément l'intérêt du découpage, l'équipe qui le maintient n'a pas à connaître l'existence du parent.

frontend/.gitlab-ci.yml
stages:
- install
- build
- test
install:
stage: install
script: npm ci
cache:
key: frontend-deps
paths:
- .npm/
variables:
npm_config_cache: '$CI_PROJECT_DIR/.npm'
build:
stage: build
script: npm run build
artifacts:
paths:
- dist/
test:
stage: test
script: npm test

Le découpage a des bornes techniques imposées par GitLab, qu'il vaut mieux connaître avant de concevoir une arborescence de pipelines. Elles poussent naturellement vers des hiérarchies plates : un parent, des enfants, et rien de plus profond. Les recommandations qui suivent découlent de ces contraintes et de la lisibilité recherchée.

Ces valeurs sont celles d'une instance GitLab par défaut ; les administrateurs d'une instance auto-hébergée peuvent ajuster la taille de la hiérarchie, mais pas la profondeur.

LimiteValeur
Profondeur child pipelines (parent → child → grandchild)2 niveaux max
Taille de la hiérarchie downstream1000 pipelines par défaut (paramétrable)
Fichiers include par child pipeline3 fichiers max

Ces cinq règles évitent les problèmes les plus coûteux : un statut vert mensonger, un fichier racine qui redevient illisible et des pipelines déclenchés pour rien. La première est la seule qui soit vraiment non négociable.

  1. Toujours strategy: depend sauf si vous voulez explicitement ne pas attendre

  2. Un fichier CI par composant : frontend/.gitlab-ci.yml, backend/.gitlab-ci.yml

  3. Conditions sur les triggers : ne déclenchez que ce qui a changé

  4. Variables explicites : documentez ce que vous passez aux enfants

  5. Nommez clairement : trigger_frontend, trigger_backend, pas job1, job2

Six symptômes reviennent systématiquement, et ils ont tous une cause simple. Un point commun les relie : la frontière entre les deux pipelines, qui bloque ce qu'on croyait partagé, ou qui laisse passer un statut qu'on croyait vérifié. Repérez votre symptôme dans les intitulés ci-dessous.

Symptôme : Le pipeline parent affiche ✅ success, mais en regardant les détails, l'enfant est en ❌ failed.

Cause : Vous avez oublié strategy: depend.

Solution :

trigger_job:
trigger:
include: child.yml
strategy: depend # 👈 Ajoutez ceci

Symptôme : L'enfant affiche $VERSION littéralement au lieu de la valeur.

Cause : La variable n'est pas passée au trigger.

Solution : Ajoutez-la dans variables: du job trigger :

trigger_job:
variables:
VERSION: $VERSION # 👈 Passez explicitement
trigger:
include: child.yml

Symptôme : GitLab refuse de créer le pipeline.

Cause : Vous avez parent → enfant → petit-enfant → arrière-petit-enfant... GitLab limite à 2 niveaux.

Solution : Restructurez pour avoir au max parent → enfant → petit-enfant.

Symptôme : file not found ou invalid config.

Cause : Le chemin dans include: est incorrect.

Solution : Le chemin est relatif à la racine du repo, pas au fichier parent :

# ❌ Si le parent est dans ci/parent.yml
trigger:
include: ../frontend/.gitlab-ci.yml # NE FONCTIONNE PAS
# ✅ Toujours depuis la racine
trigger:
include: frontend/.gitlab-ci.yml # OK

Symptôme : needs: ["build"] échoue avec "job not found".

Cause : needs classique ne traverse pas les pipelines.

Solution : Utilisez needs:pipeline:job (voir section Passer des artefacts).

Symptôme : Erreur d'autorisation.

Cause : Le projet source n'autorise pas le projet destination.

Solution : Configurez la Job token scope allowlist côté projet source (Settings → CI/CD → Token access).

  1. trigger: include crée un pipeline enfant à partir d'un fichier local
  2. strategy: depend fait attendre le parent ; mirror pour un reflet strict
  3. variables: passe des variables à l'enfant
  4. Artefacts : utilisez needs:pipeline:job (parent→child) ou needs:project (multi-projet)
  5. trigger: project déclenche un pipeline dans un autre projet
  6. Limite : 2 niveaux de profondeur, 1000 pipelines downstream max
  7. $CI_PIPELINE_SOURCE vaut parent_pipeline dans un child pipeline

Mettez ce modèle en oeuvre dans le Lab 16, Pipeline parent-enfant.

Dix questions portant sur les points où l'on se trompe en production : le comportement de strategy, la circulation des variables et la récupération des artefacts entre pipelines.

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