GitLab CI’s parent-child pipeline feature lets you break down monolithic pipelines into smaller, manageable, and reusable components.
Let’s see this in action with a practical example. Imagine you have a large application with separate services for a frontend, backend API, and a mobile app. Instead of one giant .gitlab-ci.yml file, we can orchestrate these with a parent pipeline.
Here’s a simplified parent-pipeline.yml:
stages:
- build
- test
- deploy
variables:
APP_ENV: staging
build_all:
stage: build
script:
- echo "Building all components..."
- echo "Building frontend..."
- echo "Building backend..."
- echo "Building mobile app..."
trigger_frontend_pipeline:
stage: test
trigger:
include: frontend/frontend-pipeline.yml
strategy: depend
variables:
ENV_TO_TEST: $APP_ENV
trigger_backend_pipeline:
stage: test
trigger:
include: backend/backend-pipeline.yml
strategy: depend
variables:
ENV_TO_TEST: $APP_ENV
trigger_mobile_pipeline:
stage: test
trigger:
include: mobile/mobile-pipeline.yml
strategy: depend
variables:
ENV_TO_TEST: $APP_ENV
deploy_all:
stage: deploy
needs: ["trigger_frontend_pipeline", "trigger_backend_pipeline", "trigger_mobile_pipeline"]
script:
- echo "Deploying all components to $APP_ENV..."
- echo "Deployment successful."
And here’s a sample frontend/frontend-pipeline.yml:
stages:
- build
- test
variables:
FRONTEND_BUILD_DIR: build/frontend
build_frontend:
stage: build
script:
- echo "Building frontend..."
- mkdir -p $FRONTEND_BUILD_DIR
- echo "Frontend build complete."
test_frontend:
stage: test
needs: ["build_frontend"]
script:
- echo "Running frontend tests for environment $ENV_TO_TEST..."
- echo "Frontend tests passed."
The parent-pipeline.yml defines the overall flow. The build_all job is a placeholder to represent a step that might build common artifacts or just serve as a visual grouping. The key is the trigger keyword.
When trigger_frontend_pipeline runs, it doesn’t execute jobs itself. Instead, it triggers another pipeline defined in frontend/frontend-pipeline.yml. The strategy: depend is crucial here: the parent pipeline will wait for the triggered child pipeline to complete before proceeding. Any variables defined in the parent, like APP_ENV, can be passed down to the child using the variables block within the trigger directive.
The child pipeline (frontend/frontend-pipeline.yml) then defines its own stages and jobs. Notice how test_frontend uses needs: ["build_frontend"] to ensure its own internal dependencies are met. The variable ENV_TO_TEST is available in the child pipeline because it was passed from the parent.
This structure solves a few problems at once. First, it tackles complexity. A pipeline with dozens of jobs and stages can become unmanageable. By splitting it, you isolate concerns. Frontend tests only live in the frontend pipeline, backend tests in the backend pipeline, and so on. This makes it easier to understand, debug, and maintain.
Second, it promotes reusability. If you have a common set of frontend build and test steps that are used across multiple projects or environments, you can put that logic into a separate frontend-pipeline.yml file, commit it to your repository (or even a separate template repository), and have multiple parent pipelines trigger it.
Third, it improves performance and resource utilization. Instead of one massive pipeline tying up a runner for an extended period, you have multiple, smaller pipelines running concurrently (or sequentially, depending on your strategy). This can lead to faster feedback loops and better use of your CI runner capacity.
The include keyword is the gateway to this modularity. You can include multiple files, and GitLab will merge them into a single pipeline definition. This means you can have a base gitlab-ci.yml that includes common templates, and then specific project files that include the base and add their own customizations.
When you use trigger: include: strategy: depend, the parent pipeline’s jobs that depend on the triggered pipeline (using needs) will be in a pending state until the child pipeline finishes. If the child pipeline fails, the parent pipeline will also be marked as failed at that stage, preventing subsequent stages from running.
One common pitfall is misunderstanding how variables are scoped and passed. Variables defined directly in the parent .gitlab-ci.yml are available to the trigger directive. Variables defined within the trigger directive’s variables block are passed down to the child pipeline and are available to jobs within that child. Variables defined within a child pipeline are local to that child pipeline.
The next step in mastering complex pipelines is exploring how to dynamically include different child pipelines based on conditions, or how to pass artifacts between parent and child pipelines.