As GitLab CI/CD usage grows, a single .gitlab-ci.yml file often stops being enough. Teams split systems into multiple repositories, create shared pipeline libraries, isolate deployment logic, or manage monorepos with many independently deployable parts. That is where GitLab multi-project pipelines and child pipelines become essential.
Instead of putting every job into one giant pipeline, GitLab lets one pipeline trigger another. This makes it possible to compose delivery flows across projects, share pipeline logic cleanly, and keep CI configuration maintainable even as your platform grows.
The key idea is simple: an upstream pipeline can trigger a downstream pipeline. That downstream pipeline might live in the same project as a child pipeline or in a different repository as a multi-project pipeline.
A multi-project pipeline is a pipeline in one GitLab project that triggers a pipeline in another project. This is useful when delivery spans multiple repositories.
Common reasons to use GitLab multi-project pipelines include:
There is also a related concept: parent-child pipelines. These live in the same project and are often used to break a large pipeline into smaller, focused pieces.
In practice:
trigger KeywordThe core feature is the trigger keyword.
A simple multi-project trigger looks like this:
deploy_platform:
stage: deploy
trigger:
project: my-group/platform-deploy
branch: mainWhen this job runs, GitLab starts a downstream pipeline in my-group/platform-deploy on the specified branch.
A parent-child trigger in the same project looks like this:
service_a_child:
stage: test
trigger:
include:
- local: .gitlab/child-service-a.ymlThis is ideal when the root pipeline should stay small while service-specific logic lives in separate files.
Real pipeline orchestration almost always needs context. The upstream pipeline may need to tell the downstream pipeline which image tag to deploy, which environment to use, or which feature flag to enable.
GitLab allows you to pass variables with the trigger job:
deploy_backend:
stage: deploy
trigger:
project: my-group/backend-deploy
branch: main
variables:
APP_VERSION: $CI_COMMIT_SHA
TARGET_ENV: staging
UPSTREAM_PROJECT: $CI_PROJECT_PATHThis pattern is powerful because it keeps downstream pipelines reusable. Instead of hardcoding behavior, the downstream project receives inputs and acts on them.
Typical passed variables include:
strategy: depend vs strategy: mirrorBy default, a trigger job mainly confirms that the downstream pipeline was created successfully. In many real workflows, that is not enough. You often want the upstream pipeline to reflect the downstream outcome too.
That is where trigger strategy matters.
strategy: dependUse strategy: depend when the upstream trigger job should wait for the downstream pipeline result and treat that result as meaningful for the current pipeline.
deploy_backend:
stage: deploy
trigger:
project: my-group/backend-deploy
branch: main
strategy: dependThis is a common choice when the upstream pipeline should not report success unless the downstream deployment or test pipeline also succeeds.
strategy: mirrorstrategy: mirror is used when you want the trigger job to closely reflect downstream pipeline status behavior in the GitLab UI, which can make complex orchestration easier to read. Teams often choose between depend and mirror based on how strictly they want upstream status to gate the broader workflow and on the exact behavior supported by their GitLab version.
orchestrate_release:
stage: deploy
trigger:
project: my-group/release-pipeline
branch: main
strategy: mirrorA practical rule of thumb is:
includeA child pipeline is a downstream pipeline that runs in the same project, on the same ref and commit SHA as the parent pipeline. Child pipelines are excellent for large monorepos and large CI definitions.
Example:
frontend_pipeline:
stage: test
trigger:
include:
- local: .gitlab/frontend.yml
backend_pipeline:
stage: test
trigger:
include:
- local: .gitlab/backend.ymlThis lets you split a large root file into smaller logical units while keeping everything in one repository.
Benefits of child pipelines:
include Keyword: Local, Remote, Template, ProjectThe include keyword is broader than child pipelines. It is one of the most important GitLab CI composition tools.
GitLab supports several include sources.
localUse a file from the same repository.
include:
- local: .gitlab/common.ymlremoteUse a YAML file from a remote URL.
include:
- remote: https://example.com/gitlab/common-pipeline.ymltemplateUse a built-in GitLab template.
include:
- template: Security/SAST.gitlab-ci.ymlprojectUse a file from another GitLab project.
include:
- project: my-group/pipeline-library
ref: main
file: /templates/node.ymlThis is a strong pattern for platform teams that want to publish reusable CI templates to many application repositories.
Static pipeline files are useful, but sometimes the pipeline should be generated at runtime. This is where dynamic child pipelines come in.
A dynamic child pipeline is created from a YAML file produced by an earlier job. This is especially useful when:
Example:
generate_child_config:
stage: build
script:
- python scripts/generate_pipeline.py > generated-child.yml
artifacts:
paths:
- generated-child.yml
run_generated_pipeline:
stage: test
trigger:
include:
- artifact: generated-child.yml
job: generate_child_configThis pattern makes GitLab CI much more flexible. Instead of maintaining a giant handwritten matrix, you can let the repository state drive the child pipeline definition.
needs:projectSometimes a pipeline does not need to trigger another one. It just needs to consume an artifact produced in another project. That is where needs:project becomes useful.
Example:
integration_tests:
stage: test
needs:
- project: my-group/shared-library
job: build_package
ref: main
artifacts: true
script:
- ls -la
- ./run-integration-tests.shThis allows a job to fetch artifacts from a different projectâs pipeline. It is helpful when:
This is more efficient than copying files manually or rebuilding the same artifact everywhere.
GitLab also lets you start pipelines over HTTP using the pipeline trigger API. This is useful for external orchestration, scheduled systems, or scripted integration from another CI job.
A basic curl example looks like this:
curl --request POST --form token=$CI_JOB_TOKEN --form ref=main --url "https://gitlab.example.com/api/v4/projects/42/trigger/pipeline"You can also pass variables:
curl --request POST --form token=$CI_JOB_TOKEN --form ref=main --form "variables[IMAGE_TAG]=$CI_COMMIT_SHA" --form "variables[TARGET_ENV]=staging" --url "https://gitlab.example.com/api/v4/projects/42/trigger/pipeline"This is useful when a job needs to trigger a downstream pipeline conditionally or when orchestration logic exists outside the normal GitLab trigger keyword flow.
A common architectural decision is whether to use one repository with child pipelines or multiple repositories with multi-project pipelines.
There is no universal winner. The best choice depends on ownership, deployment independence, and how often components change together.
Imagine a system with three repositories:
my-group/frontendmy-group/backendmy-group/deploymentsThe frontend repository builds the UI, the backend repository builds the API, and the deployments repository owns environment rollout logic.
stages:
- test
- deploy
frontend_tests:
stage: test
script:
- npm ci
- npm test
trigger_deploy:
stage: deploy
trigger:
project: my-group/deployments
branch: main
strategy: depend
variables:
COMPONENT: frontend
IMAGE_TAG: $CI_COMMIT_SHA
TARGET_ENV: stagingstages:
- test
- deploy
backend_tests:
stage: test
script:
- npm ci
- npm test
trigger_deploy:
stage: deploy
trigger:
project: my-group/deployments
branch: main
strategy: depend
variables:
COMPONENT: backend
IMAGE_TAG: $CI_COMMIT_SHA
TARGET_ENV: stagingstages:
- deploy
run_deploy:
stage: deploy
script:
- echo "Deploying $COMPONENT with image tag $IMAGE_TAG to $TARGET_ENV"
- ./deploy.sh "$COMPONENT" "$IMAGE_TAG" "$TARGET_ENV"
rules:
- if: '$CI_PIPELINE_SOURCE == "pipeline"'This is a good example of GitLab downstream pipeline design. The application repos keep their own tests and build logic, while the deployment repo centralizes rollout logic. The result is cleaner ownership and easier reuse.
As pipelines become more distributed, discipline matters.
Useful best practices include:
strategy: depend when downstream success must gate upstream completionThe goal is not just to make the pipeline work. The goal is to make it understandable to the next engineer who has to debug it.
GitLab multi-project pipelines and child pipelines let you scale CI/CD beyond a single flat YAML file. The trigger keyword starts downstream pipelines, variables pass context, include helps share or split configuration, dynamic child pipelines generate YAML on the fly, and needs:project can consume artifacts across repositories. Together, these features make GitLab CI/CD flexible enough for microservices, monorepos, shared deployment repos, and platform-scale orchestration.
If your pipeline is becoming too large or too tightly coupled, multi-project and child pipeline patterns are usually the next step.
<MCQExercise exercise={{ id: 'gitlab-multiproject-q1', title: 'Question 1: Pipeline Type', description: "What is the main difference between a child pipeline and a multi-project pipeline in GitLab CI/CD?", options: [ { id: 'a', text: 'A child pipeline runs in the same project, while a multi-project pipeline runs in another project' }, { id: 'b', text: 'A child pipeline can use variables, while a multi-project pipeline cannot' }, { id: 'c', text: 'A multi-project pipeline only works for Docker projects' }, { id: 'd', text: 'A child pipeline always deploys to production automatically' }, ], correctId: 'a', explanation: "Child pipelines are downstream pipelines inside the same repository, while multi-project pipelines trigger downstream pipelines in different GitLab projects.", wrongExplanations: { b: 'Both child and multi-project pipelines can receive variables.', c: 'Multi-project pipelines are not limited to Docker or any single technology stack.', d: 'Automatic production deployment is a workflow choice, not a property of child pipelines.', }, }} />
<MCQExercise exercise={{ id: 'gitlab-multiproject-q2', title: 'Question 2: Trigger Keyword', description: "What does the trigger keyword do in a GitLab CI job?", options: [ { id: 'a', text: 'It renames the current pipeline stage' }, { id: 'b', text: 'It starts a downstream pipeline in the same or another project' }, { id: 'c', text: 'It enables Docker-in-Docker automatically' }, { id: 'd', text: 'It converts artifacts into cache entries' }, ], correctId: 'b', explanation: "The trigger keyword defines a trigger job that creates a downstream pipeline, either as a child pipeline or as a multi-project pipeline.", wrongExplanations: { a: 'Stage naming is controlled by stages and stage, not by trigger.', c: 'Docker-in-Docker is configured with image, services, and runner setup, not trigger.', d: 'Artifacts and cache are separate features unrelated to the trigger keyword.', }, }} />
<MCQExercise exercise={{ id: 'gitlab-multiproject-q3', title: 'Question 3: Dynamic Child Pipelines', description: "Why would a team use a dynamic child pipeline in GitLab?", options: [ { id: 'a', text: 'To generate pipeline jobs at runtime based on changed services or other repository data' }, { id: 'b', text: 'To disable all downstream pipeline visibility in the UI' }, { id: 'c', text: 'To force every repository into a single monolithic .gitlab-ci.yml file' }, { id: 'd', text: 'To avoid using artifacts entirely' }, ], correctId: 'a', explanation: "Dynamic child pipelines are useful when the pipeline structure depends on runtime information such as changed directories, matrix targets, or generated configuration.", wrongExplanations: { b: 'Dynamic child pipelines change how configuration is created, not whether pipelines are visible.', c: 'They are used to reduce monolithic CI files, not enforce them.', d: 'Dynamic child pipelines often rely on an artifact that contains the generated YAML file.', }, }} />
By default, a triggering job succeeds as soon as the downstream pipeline has been created, without waiting to see whether it passes. strategy: depend changes that so the upstream job mirrors the downstream result. It waits for completion and fails if the downstream pipeline fails. Without it, an upstream pipeline can report green while the deployment it triggered failed, which is a genuinely misleading state and the most common surprise with this feature.
The include: keyword pulls in configuration from a local file, a remote URL, another project on the same instance, or one of GitLab's built-in templates. Including from a project is the usual choice for an internal template library, and you can pin a ref so consumers are not broken by an unreviewed change to the shared file. Combine it with YAML anchors or extends: to define a job template once and specialise it per project.
A job generates a .gitlab-ci.yml file as an artifact, and a second job triggers a child pipeline from it. Because the configuration is produced at run time, it can reflect what actually changed. A monorepo pipeline that inspects the diff and emits jobs only for affected services, for instance. It is a powerful pattern for large repositories where a static configuration would either run far too much or need constant hand-editing, at the cost of the pipeline being harder to read since it does not exist until it runs.
Variables declared under the triggering job's variables: are passed to the downstream pipeline automatically. Artifacts are not. A downstream job that needs a file from upstream fetches it with needs:project:, naming the upstream project, job and ref, which works across projects using the CI job token for authorisation. Make sure the downstream project's job token access settings permit the upstream project, since tightened defaults are a common reason cross-project fetches start failing.
Split when the components have genuinely independent release cycles, different ownership, or different access requirements. A shared library consumed by several teams, or a deployment project that holds production credentials the application repositories should not have. Keep a monorepo pipeline when components are released together, since coordinating versions across repositories is real ongoing work. For a monorepo that has simply grown unwieldy, child pipelines usually solve the problem without splitting the repository at all.
Speed up GitLab CI pipelines with better caching, DAG execution using needs, reusable include templates, YAML anchors, rules and protected deployments.
Learn what CI/CD means: how continuous integration, delivery and deployment differ, why teams need automation and what a modern pipeline looks like.
Create your first GitLab CI/CD pipeline: write a minimal three-stage .gitlab-ci.yml, understand GitLab Runners, trigger a pipeline and read the job output.