GitLab Parent/Child & Multi-Project Pipeline Design Prompt
Design parent/child pipelines, multi-project triggered pipelines, and downstream pipeline orchestration — `trigger:`, dynamic child generation, cross-project dependencies.
- Target user
- Platform engineers building complex pipeline workflows
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a senior platform engineer who has designed multi-project pipelines and parent/child pipeline workflows in production GitLab environments. You know when triggering downstream is the right answer and when it's just over-engineering. I will provide: - The use case: monorepo with per-service pipelines? Cross-project deploy dependency? Dynamic test matrix? - Current pipeline shape (single `.gitlab-ci.yml` or multiple) - Constraints: GitLab tier, who owns which project, what should block what - Symptom (if debugging an existing setup): child pipeline didn't start, parent didn't fail, variables didn't pass, status check stuck pending Your job: 1. **Decide which mechanism fits**: - **Parent → Child pipeline (single project)**: `trigger:include:` from a parent job creates a child pipeline using a separate YAML. Use for monorepos (per-package child pipelines), dynamic generation (parent emits YAML at runtime). - **Multi-project pipeline (same instance)**: `trigger:project:` triggers a pipeline in a different project. Use for cross-project deploys, library-to-consumer chains. - **API trigger (external)**: another project or tool calls GitLab API with a trigger token. Use for external orchestration. - **`needs:project:`** (downstream→upstream artifact pulls): a job in project B pulls artifacts from project A. Use sparingly — couples projects. 2. **For Parent/Child (same project)**: - Child pipeline has its own jobs, runs separately, can be inspected per child - Parent's `trigger:` job status reflects the child's status (`success`/`failed`) - `trigger:include:` accepts file paths in the repo OR `artifact:` (dynamic — generated YAML) - Dynamic child generation is powerful for monorepos: parent runs a script to detect which subprojects changed, emits a child YAML triggering only those 3. **For Multi-Project (cross-project)**: - Caller's job triggers callee's pipeline - `trigger:strategy: depend` makes the caller's job WAIT for the downstream pipeline (otherwise fire-and-forget) - Variables: only what you pass explicitly via `trigger:variables:` reaches the downstream; nothing else inherited - The triggered pipeline runs on the OTHER project's runners with the OTHER project's secrets — security boundary preserved 4. **Trigger token vs CI_JOB_TOKEN**: - **Trigger token**: project-scoped token (Settings → CI/CD → Pipeline triggers); usable from anywhere including external systems - **`$CI_JOB_TOKEN`**: implicit token of the running job; can trigger downstream pipelines if the target project allows it (Settings → CI/CD → Job token permissions in newer GitLab; previously project-wide on/off) - Prefer `$CI_JOB_TOKEN` for in-instance triggers; trigger tokens for external systems 5. **Dynamic child pipelines (monorepo pattern)**: - Parent has a `generate-pipeline` job that emits a `generated.yml` artifact based on what changed - A second parent job uses `trigger:include: artifact: generated.yml` to launch the child - This is the cleanest monorepo CI pattern in modern GitLab 6. **Variable passing**: - **Down to child**: `trigger:variables:` (only these reach the child) - **Up from child**: not directly; can use `dotenv` artifacts in the parent pipeline AFTER the child completes (with `trigger:strategy: depend`) - For multi-project: same; downstream cannot push variables back without an additional API call 7. **Common anti-patterns**: - **Triggering child for every subdir always** instead of `rules:changes:` — defeats the speedup - **`trigger:strategy: depend` everywhere** — locks parent to slowest child; consider fire-and-forget when appropriate - **Trigger loops**: child triggers parent triggers child — possible, eventually blocked, painful to debug - **Over-coupling via `needs:project:`** — project B fails when A's CI breaks; consider package registry instead - **Trigger token in YAML** — checked into git, leaks; always use `$CI_JOB_TOKEN` or masked variable 8. **For debugging**: - Child not starting → check `trigger:include:` path exists in the parent commit's tree - Cross-project trigger failing with 404 → token wrong, or project's job-token allow-list not including the caller - Status pending forever → check both projects' pipelines; downstream might be waiting on a manual approval --- Use case: [monorepo / cross-project deploy / dynamic test matrix / etc.] GitLab tier: [Free / Premium / Ultimate] Project layout: [DESCRIBE — monorepo, polyrepo, who owns what] Symptom (if debugging): [DESCRIBE] Current `.gitlab-ci.yml` (relevant): ```yaml [PASTE] ``` Cross-project relationships and triggers: [DESCRIBE]
Why this prompt works
There are 4-5 ways to compose pipelines (DAG, child, multi-project, API trigger, needs:project:) and they’re not interchangeable. Choosing the right one upfront avoids a tangled “everything triggers everything” setup. This prompt forces a use-case-first design.
How to use it
- State the use case in plain language before picking a mechanism. “I want X to happen when Y” is the right level.
- Map projects and owners. Cross-project pipelines work best when team boundaries are clear.
- Decide blocking vs. fire-and-forget upfront (
trigger:strategy:matters). - Plan for variable contract: what does downstream need? Pass explicitly.
Mechanism selector
| Need | Use |
|---|---|
| Monorepo: run only changed subproject’s CI | Dynamic child pipeline (parent generates child YAML) |
| Library A’s release should trigger consumer B’s CI | Multi-project trigger from A → B |
| External tool kicks off a pipeline | API trigger with trigger token |
| Project B needs artifacts from A’s latest main | needs:project: (couples projects; consider Package Registry) |
| Same-project, different stages, parallelize | DAG with needs: (no child pipeline needed) |
| Test matrix that’s known at YAML-write time | parallel:matrix: (no child) |
| Test matrix discovered at runtime | Dynamic child pipeline |
Patterns
Parent → Child (static)
# .gitlab-ci.yml
trigger-subproject-a:
trigger:
include: subproject-a/.gitlab-ci.yml
strategy: depend # parent waits for child
rules:
- changes:
- subproject-a/**/*
trigger-subproject-b:
trigger:
include: subproject-b/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- subproject-b/**/*
Parent → Child (dynamic, monorepo pattern)
# .gitlab-ci.yml
stages: [generate, trigger]
generate-children:
stage: generate
image: alpine:3.20
script:
- apk add --no-cache git
- ./scripts/detect-changes-and-emit-yml.sh > children.yml
artifacts:
paths: [children.yml]
expire_in: 1 hour
trigger-children:
stage: trigger
needs: [generate-children]
trigger:
include:
- artifact: children.yml
job: generate-children
strategy: depend
Multi-project trigger
# In project "platform/library-a"
deploy-and-notify-consumers:
stage: deploy
script: ./deploy.sh
trigger:
project: platform/consumer-b
branch: main
strategy: depend
variables:
UPSTREAM_VERSION: $CI_COMMIT_TAG
needs:project: (artifact pull)
# In consumer project
build:
needs:
- project: platform/library-a
job: build-binary
ref: main
artifacts: true
script:
- ls -la bin/ # has library-a's binary
(Requires target project to allow the caller in Job token permissions.)
API trigger (external)
curl --request POST \
--form "token=$TRIGGER_TOKEN" \
--form "ref=main" \
--form "variables[VERSION]=1.2.3" \
"https://gitlab.example.com/api/v4/projects/<id>/trigger/pipeline"
Common findings this catches
- Child pipeline never starts →
trigger:include:path doesn’t exist in the parent commit. Test withgit show <sha>:subdir/.gitlab-ci.yml. - Parent shows green, child failed silently → missing
strategy: depend; parent’strigger:job exits success after launching child. - Dynamic-child YAML has syntax error → no error in the parent log; the child pipeline shows as failed at the API level. Lint the generator.
- Cross-project trigger 404 / Forbidden → Job token permissions in the target project don’t include the source. Allow in Settings → CI/CD.
- Variables not appearing in child →
trigger:variables:only passes the listed ones. Add what you need. - Trigger loop (A triggers B triggers A) → cycle protection eventually kicks in, but redesign — usually a misunderstood ownership.
When to escalate
- Trigger relationships forming a dependency graph nobody understands — diagram it, simplify, prefer Package Registry for library handoffs.
- Cross-team triggers without clear ownership of failures — establish on-call for downstream pipelines.
- Trigger tokens leaked to git history — rotate immediately, scrub history, audit pipeline runs since the leak.
Related prompts
-
GitLab CI/CD `include:` & Template Refactor Prompt
Refactor a large `.gitlab-ci.yml` using `include:`, hidden jobs (`.template:`), `!reference`, and `extends:` — without losing override flexibility.
-
GitLab CI/CD `needs:` DAG Optimization Prompt
Convert stage-based GitLab pipelines to DAG (`needs:`), find hidden ordering bugs, design clean fan-out/fan-in patterns, and avoid `needs:` traps.
-
GitLab CI/CD Pipeline Optimization Prompt
Speed up slow GitLab pipelines — DAG with `needs:`, cache vs artifacts, parallel jobs, image pre-builds, dependency proxy, and shallow clones.