Parent-Child Pipelines, Dynamic Child Pipelines, Generated Configuration, and Monorepo Decomposition: Concepts, Architecture, and Mental Model
Parent-child pipelines let one repository keep one source revision while decomposing CI/CD into smaller pipeline records. This lesson builds the mental model from the parent source SHA and trigger job through static or generated child configuration, child pipeline identity, status/report propagation, and bounded evidence.
Learning objectives
- Explain parent-child pipelines as multiple pipeline records that share one project, ref, and source SHA but compile and execute separate child job graphs.
- Trace parent pipeline → trigger job → static/generated child configuration → child pipeline → jobs/reports/artifacts → parent evidence.
- Distinguish trigger-job creation success from child pipeline status, and explain when strategy: mirror changes that contract.
- Identify current hierarchy limits, nesting depth, pipeline-source semantics, variable/input forwarding, and dynamic-config constraints.
- Inspect parent/child IDs, shared SHA, trigger configuration, child source, and non-secret evidence before changing orchestration.
1. The practical problem: one repository can outgrow one pipeline graph
By Chapter 13 you can package reusable pipeline logic as versioned components. That does not solve a different scaling problem: a monorepo may contain many independently owned applications, libraries, and infrastructure folders whose jobs do not need to appear in one giant graph on every change.
Parent-child pipelines split orchestration into multiple pipeline records while preserving one repository and one source revision. The split improves ownership and graph readability only when the boundaries remain traceable. If teams cannot answer which parent SHA created a child, which child configuration was compiled, what was forwarded, and which status the parent is actually observing, decomposition has traded one form of complexity for another.
2. Keep the states separate before changing anything
| State | Question to ask | Evidence |
|---|---|---|
| Repository/source | Which project, ref, and exact SHA caused the hierarchy? | Parent project/ref/SHA and child SHA |
| Parent configuration | Which trigger job and rules selected a child? | Expanded parent YAML, trigger job name/rule result |
| Child configuration | Was the child static, merged from includes, or generated as an artifact? | Child file/ref or generated YAML artifact + producer job |
| Pipeline records | Which parent and child pipeline IDs exist? | Parent pipeline ID, child pipeline ID, relation in graph/API |
| Forwarded data | Which inputs/variables crossed the boundary? | Explicit non-secret inputs/variables; trigger:forward policy |
| Runtime | Which child jobs queued and on which runners? | Child job IDs/statuses, runner/executor/version |
| Evidence/output | Which reports/artifacts belong to which child job/SHA? | Producer job, artifact/report metadata, digest where useful |
| Governance | Who owns the child boundary and how large may fan-out become? | Ownership map, hierarchy/job limits, review/rollback policy |
3. Mental model: parent source → trigger boundary → child graph
The parent pipeline is created for a project/ref/SHA. Its compiled jobs include one or more trigger jobs. A trigger job does not execute a shell script on a runner; GitLab evaluates its downstream configuration and attempts to create a child pipeline. Static child configuration already exists in the repository. Dynamic child configuration is first generated by an ordinary job, retained as an artifact, then consumed by a trigger job.
The child is a separate pipeline record, but for a parent-child relationship it runs in the same project, ref, and commit SHA. That shared SHA is the strongest anchor for proving that the parent and child describe one source revision.
4. Same source does not mean same configuration record
Parent and child pipelines share the source SHA, but each pipeline compiles its own configuration. A child can therefore fail configuration while the parent configuration is valid, omit all jobs because child rules do not match, or create a different DAG than the parent author expected.
Current child jobs see
CI_PIPELINE_SOURCE=parent_pipeline. They do not see
merge_request_event as their pipeline source even when
the parent was a merge-request pipeline. Merge-request predefined
variables are still available to the child, so child rules that need
MR context should test those variables rather than misclassifying
the child source.
child-test:
rules:
- if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
script:
- printf 'source=%s\n' "$CI_PIPELINE_SOURCE"
- printf 'sha=%s\n' "$CI_COMMIT_SHA"
5. A trigger job proves creation unless you ask for status coupling
By default, a trigger job becomes successful when GitLab successfully creates the downstream pipeline. The child can later fail without changing that already-successful trigger job. That default is useful when the parent should launch independent work, but it is unsafe to interpret the green trigger as “all child work passed.”
Current GitLab recommends strategy: mirror when the
trigger job should mirror the child pipeline status. The older
strategy: depend remains available but is not
recommended for new designs because its status mapping has edge
cases.
test-api:
stage: test
trigger:
include:
- local: ci/children/api.yml
strategy: mirror
6. Static and dynamic child configuration have different trust surfaces
| Child type | Configuration identity | Primary risk | Best evidence |
|---|---|---|---|
| Static local | File at the shared repository SHA | Stale ownership/rules or hidden include changes | Exact source SHA + child file path + expanded child config |
| Static project/template | External config source/ref plus parent SHA | Dependency drift/permission assumptions | Pinned source/ref + expanded config |
| Dynamic artifact | Generator source + generator inputs + produced YAML artifact | Injection/unbounded generation/wrong metadata | Generator SHA/job + validated inputs + generated YAML digest + child ID |
7. Dynamic child configuration is generated code
A dynamic child pipeline treats a generated YAML artifact as executable configuration. The safe mental model is not “string template convenience”; it is “a compiler emits another program.” Inputs to the generator therefore require the same distrust you would apply to code-generation inputs.
Constrain metadata to an allowlist, reject unknown keys, bound the number of emitted jobs, use fixed job templates, validate the generated YAML, and retain the generated artifact before GitLab consumes it. Never concatenate branch names, MR titles, filenames, API text, or user variables directly into YAML keys or shell commands.
8. Hierarchy limits are reliability controls, not obstacles to remove
Current GitLab allows two child nesting levels and limits the whole downstream hierarchy to 1000 pipelines by default. These limits protect the instance from recursive or explosive creation. A monorepo design that needs hundreds of children per commit should first question its decomposition model, rules, and granularity rather than immediately raising instance limits.
Bound both pipeline count and job count per child. Fan-out also consumes runners, queue capacity, artifacts, logs, API calls, and human attention.
9. Inputs and variables are dataflow contracts across the boundary
Prefer explicit downstream inputs when the child exposes a stable
configuration interface. For variables, remember that YAML-defined
variables on the trigger job are forwarded by default, while
pipeline variables are not forwarded unless
trigger:forward:pipeline_variables: true is configured.
Forwarded variables have high precedence in the downstream pipeline.
Do not forward secrets “because the child might need them.” Narrow the child’s identity and secret access independently. Forward only non-secret configuration the child contract requires, or use an approved secret mechanism inside the authorized child job.
variables:
GLOBAL_CONTEXT: "not-needed-in-child"
api-child:
inherit:
variables: false
variables:
PARENT_PIPELINE_ID: "$CI_PIPELINE_ID"
MONOREPO_AREA: "api"
trigger:
include:
- local: ci/children/api.yml
strategy: mirror
forward:
yaml_variables: true
pipeline_variables: false
10. Status, reports, and artifacts cross boundaries differently
Status coupling is controlled by trigger strategy. Report visibility has its own rules: current GitLab can surface supported child-pipeline reports in merge-request views when the parent waits appropriately. Raw artifact transfer is a separate dataflow problem.
For parent-child artifact download,
needs:pipeline:job can fetch artifacts from a
successful job elsewhere in the same hierarchy, but that capability
is currently Premium/Ultimate. The mandatory path in this chapter
therefore gives each child everything it needs from repository
source or its own jobs and treats cross-boundary artifact fetching
as an optional architecture exercise.
11. Read-only inspection sequence
- Record parent pipeline ID, source, project, ref, and exact SHA.
- Inspect the parent expanded configuration and the trigger job’s rules/include/strategy/forwarding policy.
- Identify whether the child configuration is repository-backed or artifact-generated.
- For generated YAML, inspect the generator job, exact inputs, artifact path, digest, and YAML content before triggering.
-
Record child pipeline ID and confirm the same project/ref/SHA plus
source=parent_pipeline. - Inspect child expanded configuration/job graph and then runtime job/runner evidence.
- Only after those states are known, interpret parent/child status, reports, artifacts, or external side effects.
12. Common wrong models
- “The child is just another stage.” No. It is a separate pipeline record with its own compiled configuration and job graph.
-
“A green trigger means the child passed.” Only
with a status-coupling strategy such as
mirror; default success means the child was created. - “Dynamic YAML is safe because GitLab validates YAML.” YAML validity does not prevent attacker-controlled job names, scripts, includes, images, variables, or explosive fan-out.
-
“The child is a merge-request pipeline if the parent
is.”
The child source remains
parent_pipeline; use MR variables for MR-specific child behavior. - “Forward all parent variables for convenience.” This expands hidden coupling and can leak sensitive or high-precedence data.
Knowledge check
What identity should parent and child always share in a parent-child pipeline?
They run in the same project, ref, and commit SHA. Record the exact SHA in both pipeline records.
Why can a green trigger job be misleading?
Without a strategy, success proves the downstream pipeline was created, not that its jobs later succeeded. Use strategy: mirror when the trigger must reflect child status.
What value does CI_PIPELINE_SOURCE have inside a child pipeline?
parent_pipeline. If the parent was a merge-request pipeline, use inherited merge-request variables for MR-specific child rules.
Why must generated child YAML be retained as evidence?
It is executable configuration. The artifact plus generator identity and validated inputs proves what GitLab attempted to compile.
Is needs:pipeline:job required for parent-child pipelines?
No. Parent-child orchestration itself is Free. needs:pipeline:job is an optional Premium/Ultimate artifact-transfer feature and is not required for the mandatory labs.
Version and compatibility note
GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.
Official references and version notes
Documentation verification date: 2026-09-11.
Parent/child pipeline syntax, downstream status behavior, report
visibility, hierarchy limits, inputs, and variable forwarding are
version-sensitive. Re-check the deployed GitLab version before
relying on newer syntax such as strategy: mirror.
- Downstream pipelines — parent-child semantics, dynamic child pipelines, nesting, pipeline source, reports, variables, inputs, and status behavior.
-
CI/CD YAML syntax reference
—
trigger,trigger:include,trigger:strategy,trigger:forward, andneeds:pipeline:job. - Use CI/CD configuration from other files — include rules and dynamic-child configuration limitations.
- Troubleshooting downstream pipelines — empty child pipelines, permission/configuration failures, and variable forwarding issues.
- CI/CD limits — downstream hierarchy sizing and resource-protection rationale.
-
Pipelines API
— listing pipelines and querying child pipelines with
source=parent_pipeline.
CI_PIPELINE_SOURCE=parent_pipeline. Nested child
pipelines are limited to two child levels; the default pipeline
hierarchy limit is 1000 downstream pipelines. One child trigger can
combine up to three child configuration files. Dynamic child
configuration can come from a generated artifact; the artifact path
is interpreted by the GitLab server, and CI/CD variables cannot be
used in an include section inside the dynamic child
configuration. strategy: mirror was introduced in
GitLab 18.2 and is the recommended status-coupling strategy; without
a strategy the trigger job succeeds once the child is created.
YAML-defined trigger variables forward by default, while pipeline
variables do not unless
trigger:forward:pipeline_variables: true is set.
needs:pipeline:job for cross-parent/child artifact
download is currently Premium/Ultimate, so mandatory labs do not
depend on it.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.