Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Multi-project GitLab CI/CD pipelines (trigger downstream)

By updated

Improve this page

GitLab logo

Does your frontend application need to trigger the backend deployment after a successful build? Multi-project pipelines (trigger: project:) let you launch another project's pipeline from yours. You can pass variables, wait for the result and build deployment chains between independent projects.

If parent-child pipelines orchestrate sub-pipelines within the same project (monorepo), multi-project pipelines orchestrate different projects that each have their own .gitlab-ci.yml.

What you will learn

  • Trigger another project's pipeline with trigger: project:
  • Propagate variables to the target project
  • Wait for the downstream pipeline result with strategy: mirror (GitLab 18.2 and later)
  • Choose between parent-child and multi-project depending on your architecture
  • Secure triggers between projects

Prerequisites

This lesson assumes you already write multi-job pipelines and know how to drive them with conditions. You also need two GitLab projects on the same instance, at least the Developer role in the one you want to trigger (see the "Permissions and security" section), and GitLab 18.2 or later for strategy: mirror.

In which context?

Your GitLab projects are split into separate repositories (frontend, backend, infra, docs). When the backend changes, you need to rerun the frontend's integration tests. When a Docker image is published, the deployment project must follow.

This guide is for you if:

  • You have an API project that, after the build, must trigger the deployment
  • Your shared library must test the projects that depend on it
  • Your release pipeline coordinates several microservices
  • You want an integration pipeline that validates compatibility between projects

Parent-child vs Multi-project

Both mechanisms share the same trigger: keyword and are easily confused, even though they do not answer the same need. The selection criterion comes down to one question: does the triggered code live in the same repository? If so, it is parent-child; otherwise it is multi-project, with the permission and variable-propagation constraints that come with it.

CriterionParent-childMulti-project
ScopeSame project (same repo)Different projects (different repos)
.gitlab-ci.ymlThe parent generates/references the childrenEach project has its own pipeline
Use caseMonorepo, submodulesMicroservices, deployment chain
Syntaxtrigger: include:trigger: project:
LimitsMax 2 levels of depthNo chain limit
VariablesAutomatic inheritanceExplicit propagation

If your code is in a single repository, use parent-child pipelines.

How do you trigger a downstream pipeline?

Triggering relies on a special job: it has no script: but a trigger: key designating the target project. GitLab calls the pipeline that triggers upstream and the one that is triggered downstream. Three settings are enough to cover almost every need: the target project, the branch, and whether or not to wait for the result.

Basic syntax

The path passed to project: is the full path of the repository in GitLab (group, optional subgroup, then project name), not its URL nor its numeric ID.

Project A's .gitlab-ci.yml (source)
stages:
- build
- deploy
build:
stage: build
script:
- npm run build
trigger:deploy:
stage: deploy
trigger:
project: devops/deploy-service
branch: main

What happens:

  1. Project A's pipeline runs the build job
  2. The trigger:deploy job triggers the pipeline of project devops/deploy-service on the main branch
  3. A's pipeline continues without waiting for the result (by default)

Propagating variables

To pass information to the target project, use variables::

Source project
trigger:deploy:
stage: deploy
trigger:
project: devops/deploy-service
branch: main
variables:
UPSTREAM_PROJECT: $CI_PROJECT_PATH
UPSTREAM_REF: $CI_COMMIT_REF_NAME
UPSTREAM_SHA: $CI_COMMIT_SHORT_SHA
APP_VERSION: $BUILD_VERSION

The target project receives these variables as regular environment variables:

Target project (devops/deploy-service)
deploy:
stage: deploy
script:
- echo "Triggered by $UPSTREAM_PROJECT @ $UPSTREAM_SHA"
- echo "Deploying version $APP_VERSION"
- ./deploy.sh --version "$APP_VERSION"

Waiting for the result: strategy: mirror

By default, the trigger job turns success as soon as the downstream pipeline is created (not finished). To wait, add strategy: mirror, available since GitLab 18.2:

trigger:deploy:
stage: deploy
trigger:
project: devops/deploy-service
branch: main
strategy: mirror
variables:
APP_VERSION: $BUILD_VERSION

With strategy: mirror, the trigger job exactly mirrors the status of the downstream pipeline:

Downstream statusTrigger job status
SuccessSuccess
FailureFailure
RunningRunning
Failed job with allow_failure: trueSame status as the downstream (the failure is not hidden)

This is almost always what you want. Without strategy:, your source pipeline shows "success" while the downstream deployment may have failed.

What about strategy: depend? It is the older value, which the GitLab documentation marks "Not recommended": it is still accepted, but no longer recommended. Keep it for instances older than 18.2, where mirror does not exist. The reason not to use it anymore: depend counts as successful a failed downstream pipeline in which a job has allow_failure: true, whereas mirror does not hide that failure. The parent-child lesson details the difference between the two values.

Targeting a specific branch

Reusing $CI_COMMIT_REF_NAME assumes that a branch with the same name exists in the target project: otherwise the trigger fails, whereas a fixed branch always resolves.

# Fixed branch
trigger:
project: devops/deploy-service
branch: main
# Same branch as the source (if it exists in the target project)
trigger:
project: devops/deploy-service
branch: $CI_COMMIT_REF_NAME

Common use cases

The three situations that follow cover most of the usages met in an organization split into several repositories. They differ by the direction of the trigger and by what you expect from the downstream pipeline: coordinate a release, validate consumers, or simply notify.

Microservices deployment chain

An orchestrator project, which often contains only a .gitlab-ci.yml, triggers the others. The rules: on the tag guarantees that nothing starts on a plain branch commit, and jobs of the same stage run in parallel.

Orchestrator project
stages:
- trigger
trigger:api:
stage: trigger
trigger:
project: team/api-service
strategy: mirror
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/
trigger:frontend:
stage: trigger
trigger:
project: team/frontend-app
strategy: mirror
variables:
API_VERSION: $CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/
trigger:docs:
stage: trigger
trigger:
project: team/documentation
# No strategy: we do not wait for the docs
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/

Testing a library's dependents

When your library changes, run the tests of the projects that use it:

Library project
stages: [test, verify]
test:library:
stage: test
script:
- npm test
# After the tests, check that the consumers do not break
verify:app-a:
stage: verify
trigger:
project: team/app-a
strategy: mirror
variables:
LIB_VERSION: $CI_COMMIT_SHA
verify:app-b:
stage: verify
trigger:
project: team/app-b
strategy: mirror
variables:
LIB_VERSION: $CI_COMMIT_SHA

Reverse pipeline: the target triggers via the API

The reverse pipeline goes through the API rather than the trigger: project: keyword, which is not always usable: the target project may live on another GitLab instance, or an external tool must start its pipeline. The target project is then triggered from the outside: you create a pipeline trigger token in its Settings > CI/CD > Pipeline trigger tokens menu, then the caller (a job, a webhook) sends a request to its API. The example below has a job of the source project call that API:

Source project: trigger via the API in a script
stages: [notify]
notify:deploy:
stage: notify
script:
- |
curl --request POST \
--form "token=$DEPLOY_TRIGGER_TOKEN" \
--form "ref=main" \
--form "variables[UPSTREAM_SHA]=$CI_COMMIT_SHORT_SHA" \
"https://gitlab.example.com/api/v4/projects/42/trigger/pipeline"

How do you control triggers with rules?

Trigger jobs support rules: like any other job. It is the way to open the trigger only under the wanted conditions: the default branch for a staging environment, a version tag for production. Adding when: manual to the latter imposes a human approval before the downstream pipeline starts, and allow_failure: false guarantees that its failure will fail the source pipeline.

trigger:staging:
trigger:
project: devops/deploy-service
strategy: mirror
variables:
ENVIRONMENT: staging
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
trigger:production:
trigger:
project: devops/deploy-service
strategy: mirror
variables:
ENVIRONMENT: production
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
allow_failure: false

Permissions and security

A multi-project trigger crosses a boundary: it makes code run in a repository you are not necessarily responsible for. GitLab frames this with two separate controls. The first is about the identity of the user who launched the source pipeline: that user must be able to start a pipeline in the target project, and this control applies to trigger: project:. The second, the list of projects allowed to use the job token, comes into play when a job calls another project's API with that token. The documentation of trigger: project: does not require it for the keyword itself.

Who can trigger what?

The downstream pipeline is created on behalf of the user who triggered the source pipeline. That user must have at least the Developer role in the target project.

Role in the target projectCan trigger?
GuestNo
ReporterNo
DeveloperYes
MaintainerYes
OwnerYes

CI_JOB_TOKEN

Since GitLab 16.3+, the CI_JOB_TOKEN can be used for cross-project authentication. Configure it in the target project: Settings > CI/CD > Job token permissions, CI/CD job token allowlist section, and add the source project to the list of allowed projects. This list applies to API calls made with the token, not as a prerequisite of the trigger: project: keyword.

Limits and best practices

Multi-project pipelines impose almost no technical limit, which is precisely the risk: nothing prevents building a chain of six projects whose order nobody understands anymore. The real constraints are operational, and the few rules below are enough to keep the whole thing readable.

Limits

Mostly remember the last row: it is not a cap but a behavior. Any variable not listed in variables: stays in the source project, including those defined in the GitLab interface.

LimitValue
Downstream chain depthNo strict limit, but each level adds latency
Concurrent pipelines per projectDepends on your license and the project configuration
Variables passedNo limit, but only the variables declared in variables: are propagated

Best practices

These five reflexes avoid the two costliest failures in multi-project: a deployment that believes it succeeded, and a coupling between repositories that nobody documented.

  • Use strategy: mirror (GitLab 18.2 and later) except for non-critical triggers (docs, notifications): it does not hide the failure of a downstream job with allow_failure: true, unlike depend, which you keep only on an instance older than 18.2
  • Name jobs clearly: trigger:deploy-staging, not downstream_1
  • Propagate the minimum of variables needed, no secrets from the source project
  • Document the dependencies between projects in the README
  • Version via tags: target branch: main for the latest state, a tag for a specific version

Troubleshooting

The difficulty of multi-project is that the error shows up in one repository and its cause is in the other. So start by looking at the downstream pipeline itself: if it was never created, the problem is about permissions or the project path; if it exists but fails, it is a matter of variables or branch.

SymptomLikely causeSolution
403 ForbiddenThe user has no rights on the target projectCheck the role (minimum Developer)
Downstream pipeline not createdThe target project does not exist or is privateCheck the project: path and permissions
Trigger job "success" but downstream failedstrategy: missing, or depend with a downstream job in allow_failure: trueAdd strategy: mirror (GitLab 18.2+)
Variables not available in the downstreamNot declared in variables:Explicitly add each variable to pass
Infinite trigger loopProject A triggers B which triggers AAdd a rules condition to break the loop
Downstream pipeline on the wrong branchbranch: missingSpecify branch: explicitly

Key takeaways

  • trigger: project: triggers a pipeline in another GitLab project
  • strategy: mirror (GitLab 18.2+) makes the trigger wait for the result and exactly mirrors the downstream status; without strategy:, the trigger job turns success as soon as the pipeline is created. depend, not recommended, remains only for instances older than 18.2
  • Variables must be propagated explicitly, no automatic inheritance
  • The user who triggers must have at least the Developer role in the target project
  • Parent-child = same repo (monorepo), multi-project = different repos (microservices)
  • Trigger jobs cannot have a script:, it is a trigger, not an executor
  • Always document the dependencies between projects, triggers create an implicit coupling

Test your knowledge

These questions only cover the notions explained above: triggering, variable propagation, waiting for the result with strategy: and permissions.

Knowledge check

Check what you have learned with this interactive quiz

10 questions
5 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn