
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.
- Writing a .gitlab-ci.yml file: stages, jobs and
script: - Variables and secrets: declaring a variable and reading it in a job
- Run conditions (rules): enabling or skipping a job depending on context
- Parent-child pipelines: the
trigger:keyword and the idea of a triggered pipeline
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.
| Criterion | Parent-child | Multi-project |
|---|---|---|
| Scope | Same project (same repo) | Different projects (different repos) |
.gitlab-ci.yml | The parent generates/references the children | Each project has its own pipeline |
| Use case | Monorepo, submodules | Microservices, deployment chain |
| Syntax | trigger: include: | trigger: project: |
| Limits | Max 2 levels of depth | No chain limit |
| Variables | Automatic inheritance | Explicit 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.
stages: - build - deploy
build: stage: build script: - npm run build
trigger:deploy: stage: deploy trigger: project: devops/deploy-service branch: mainWhat happens:
- Project A's pipeline runs the
buildjob - The
trigger:deployjob triggers the pipeline of projectdevops/deploy-serviceon themainbranch - A's pipeline continues without waiting for the result (by default)
Propagating variables
To pass information to the target project, use variables::
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_VERSIONThe target project receives these variables as regular environment variables:
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_VERSIONWith strategy: mirror, the trigger job exactly mirrors the status of the downstream pipeline:
| Downstream status | Trigger job status |
|---|---|
| Success | Success |
| Failure | Failure |
| Running | Running |
Failed job with allow_failure: true | Same 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 branchtrigger: 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_NAMECommon 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.
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:
stages: [test, verify]
test:library: stage: test script: - npm test
# After the tests, check that the consumers do not breakverify: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_SHAReverse 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:
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: falsePermissions 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 project | Can trigger? |
|---|---|
| Guest | No |
| Reporter | No |
| Developer | Yes |
| Maintainer | Yes |
| Owner | Yes |
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.
| Limit | Value |
|---|---|
| Downstream chain depth | No strict limit, but each level adds latency |
| Concurrent pipelines per project | Depends on your license and the project configuration |
| Variables passed | No 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 withallow_failure: true, unlikedepend, which you keep only on an instance older than 18.2 - Name jobs clearly:
trigger:deploy-staging, notdownstream_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: mainfor 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.
| Symptom | Likely cause | Solution |
|---|---|---|
403 Forbidden | The user has no rights on the target project | Check the role (minimum Developer) |
| Downstream pipeline not created | The target project does not exist or is private | Check the project: path and permissions |
| Trigger job "success" but downstream failed | strategy: missing, or depend with a downstream job in allow_failure: true | Add strategy: mirror (GitLab 18.2+) |
| Variables not available in the downstream | Not declared in variables: | Explicitly add each variable to pass |
| Infinite trigger loop | Project A triggers B which triggers A | Add a rules condition to break the loop |
| Downstream pipeline on the wrong branch | branch: missing | Specify branch: explicitly |
Key takeaways
trigger: project:triggers a pipeline in another GitLab projectstrategy: mirror(GitLab 18.2+) makes the trigger wait for the result and exactly mirrors the downstream status; withoutstrategy:, 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
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
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: GitLab CI/CD workflows (MR, branches, releases)
A new full quiz with random questions
Work again only on the questions you missed