You can trigger downstream GitLab pipelines across projects, but it’s not a direct, built-in feature of GitLab CI/CD itself. Instead, it’s achieved by leveraging GitLab’s API to initiate a new pipeline run in a different project.

Let’s see this in action. Imagine you have a "build" project that, upon successful completion, needs to trigger a "deploy" pipeline in a separate "production" project.

Here’s a simplified .gitlab-ci.yml in the "build" project that does this:

stages:
  - build
  - trigger_deploy

build_app:
  stage: build
  script:
    - echo "Building the application..."
    # Actual build commands here
    - echo "Build complete."

trigger_production_deploy:
  stage: trigger_deploy
  script:
    - |
      curl --request POST "https://gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/ref/${CI_COMMIT_REF_NAME}/trigger/pipeline" \
        --header "PRIVATE-TOKEN: ${GITLAB_TRIGGER_TOKEN}" \
        --form "variables[DEPLOY_ENV]=production" \
        --form "variables[BUILD_ARTIFACT_URL]=http://your-artifact-repo.com/build-${CI_COMMIT_SHA}.tar.gz"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: on_success

In this example:

  • build_app is a standard job that performs the build.
  • trigger_production_deploy is the job that initiates the downstream pipeline.
  • It uses curl to make a POST request to the GitLab API’s pipeline trigger endpoint.
  • CI_PROJECT_ID and CI_COMMIT_REF_NAME are predefined CI/CD variables that refer to the current project and branch.
  • GITLAB_TRIGGER_TOKEN is a project-level CI/CD variable that holds a personal access token or a project access token with api scope. This is crucial for authentication.
  • variables[DEPLOY_ENV]=production and variables[BUILD_ARTIFACT_URL] are examples of how you can pass variables to the downstream pipeline. These would then be accessible in the .gitlab-ci.yml of the "production" project.
  • The rules ensure this job only runs on the main branch and only if the preceding build_app job succeeds.

The "production" project’s .gitlab-ci.yml would look something like this:

stages:
  - deploy

deploy_to_production:
  stage: deploy
  script:
    - echo "Deploying to production environment..."
    - echo "Using deployment environment: $DEPLOY_ENV"
    - echo "Artifact URL: $BUILD_ARTIFACT_URL"
    # Actual deployment commands here
    - echo "Deployment complete."
  rules:
    - if: '$CI_PIPELINE_SOURCE == "trigger"' # This job runs when triggered by the API

The key here is CI_PIPELINE_SOURCE == "trigger". This rule ensures that the deploy_to_production job only runs when the pipeline is initiated via an API trigger, preventing it from running on regular pushes.

The problem this solves is creating robust, multi-project CI/CD workflows where distinct stages of your development lifecycle (e.g., building, testing, deploying to staging, deploying to production) can reside in separate, independently managed GitLab projects. This promotes modularity, better access control, and clearer ownership of different pipeline stages.

The GitLab API endpoint projects/:id/trigger/pipeline is designed for this. It takes a reference (like a branch name) and optional variables. When you call it, GitLab spins up a new pipeline in the target project, associated with the specified ref, and passes along any variables you defined. The PRIVATE-TOKEN or CI_JOB_TOKEN (if the projects are within the same GitLab instance and you’re using a CI job token with appropriate permissions) is the credential that grants permission to perform this action.

What most people don’t realize is that you can trigger pipelines in any project you have API access to, not just within the same GitLab instance. If you have a personal access token with api scope, you can trigger pipelines across different GitLab.com accounts or even between a self-hosted GitLab and GitLab.com, provided the token is valid. This opens up possibilities for complex, distributed CI/CD setups.

Once you have this working, the next logical step is to manage the bidirectional flow of information, such as reporting the success or failure of the downstream pipeline back to the upstream one.

Want structured learning?

Take the full Gitlab course →