Chapter 14Lesson 02~200 minutes

Parent-Child Pipelines, Dynamic Child Pipelines, Generated Configuration, and Monorepo Decomposition: Guided Hands-On Workflow and Core Operations

This guided workflow builds a disposable toy monorepo with two static child pipelines, then adds a safely generated child configuration from allowlisted metadata. You will trigger only the intended subtree, inspect parent/child IDs and shared SHA, and compare default trigger behavior with status mirroring.

Parent-child pipelinesDynamic configurationMonoreposGenerated YAMLPipeline orchestration

Learning objectives

  • Create two static child pipeline files for separate monorepo subtrees and trigger them intentionally.
  • Generate a small child YAML artifact only from allowlisted metadata and validate the generated content before GitLab consumes it.
  • Use rules:changes to trigger only the intended subtree and explain the pipeline-creation consequences.
  • Record parent/child pipeline IDs, source/ref/SHA, trigger-job status, child pipeline source, and observed job/artifact evidence.
  • Use a free/disposable path and complete a challenge that selects the correct orchestration/dataflow/security layer.

1. Guided scenario: a two-area disposable monorepo

Create a throwaway project with two synthetic areas: services/api/ and services/web/. Each area has a text file and its own child pipeline. Nothing deploys, publishes packages, touches cloud infrastructure, or requires privileged runners.

.
├── .gitlab-ci.yml
├── ci/
│   ├── children/
│   │   ├── api.yml
│   │   └── web.yml
│   └── generate_child.py
├── services/
│   ├── api/app.txt
│   └── web/app.txt
└── generated/

2. Preflight and assumptions

  • Use a disposable GitLab project you are authorized to modify.
  • Use GitLab Free-compatible parent-child pipelines. No catalog, protected environment, cloud, Kubernetes, or admin control is required.
  • A normal non-privileged runner is sufficient for jobs that execute scripts. Trigger jobs themselves do not use runners.
  • Record the GitLab offering/version visible to you and the Runner version/executor if runtime jobs execute.
  • Do not create or print secrets. All variables in this lab are synthetic identifiers.

3. Static child A: API

stages: [verify]

api-verify:
  stage: verify
  image: alpine:3.20.3
  rules:
    - if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
  script:
    - test -f services/api/app.txt
    - mkdir -p evidence
    - printf 'area=api\nsha=%s\nchild_pipeline=%s\nsource=%s\n'         "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_PIPELINE_SOURCE" > evidence/api.txt
  artifacts:
    expire_in: 1 day
    paths: [evidence/api.txt]

4. Static child B: web

stages: [verify]

web-verify:
  stage: verify
  image: alpine:3.20.3
  rules:
    - if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
  script:
    - test -f services/web/app.txt
    - mkdir -p evidence
    - printf 'area=web\nsha=%s\nchild_pipeline=%s\nsource=%s\n'         "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_PIPELINE_SOURCE" > evidence/web.txt
  artifacts:
    expire_in: 1 day
    paths: [evidence/web.txt]

5. Parent pipeline: trigger only changed subtrees

stages: [children]

api-child:
  stage: children
  rules:
    - changes:
        - services/api/**/*
  variables:
    PARENT_PIPELINE_ID: "$CI_PIPELINE_ID"
    MONOREPO_AREA: "api"
  trigger:
    include:
      - local: ci/children/api.yml
    strategy: mirror

web-child:
  stage: children
  rules:
    - changes:
        - services/web/**/*
  variables:
    PARENT_PIPELINE_ID: "$CI_PIPELINE_ID"
    MONOREPO_AREA: "web"
  trigger:
    include:
      - local: ci/children/web.yml
    strategy: mirror

Make an API-only change. Prediction: the parent contains the API trigger but omits the web trigger; the created child has the same SHA as the parent and its jobs report parent_pipeline. Preserve the graph and IDs before changing the project again.

6. Evidence after the static run

Observation Expected proof
Parent source/ref/SHA Record from pipeline details/job-safe predefined variables
Parent trigger jobs API present; web omitted for API-only change
Child ID Visible from downstream card/details; API can also query with appropriate access
Child source parent_pipeline
Child SHA Exactly equals parent CI_COMMIT_SHA
Trigger status Mirrors child because strategy: mirror is used
Artifact evidence/api.txt belongs to child job and contains only synthetic metadata

7. Add a bounded generator instead of templating raw user input

The generator accepts exactly one area from a hard-coded allowlist and emits a fixed job shape. It does not accept raw YAML, shell fragments, image names, includes, or arbitrary job counts.

from pathlib import Path
import os, sys

allowed = {"api", "web"}
area = os.environ.get("AREA", "")
if area not in allowed:
    raise SystemExit(f"AREA must be one of {sorted(allowed)}")

job = f"""{area}-generated-verify:
  image: alpine:3.20.3
  script:
    - test -f services/{area}/app.txt
    - mkdir -p evidence
    - printf 'area={area}\\nsha=%s\\nchild_pipeline=%s\\nsource=%s\\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_PIPELINE_SOURCE" > evidence/{area}-generated.txt
  artifacts:
    expire_in: 1 day
    paths:
      - evidence/{area}-generated.txt
"""
Path("generated").mkdir(exist_ok=True)
Path("generated/child.yml").write_text(job, encoding="utf-8")

8. Generate, inspect, then trigger the dynamic child

stages: [generate, children]

generate-api-child:
  stage: generate
  image: python:3.13.7-alpine3.22
  variables:
    AREA: "api"
  script:
    - python ci/generate_child.py
    - test "$(grep -c '^[a-z].*-generated-verify:' generated/child.yml)" -eq 1
    - sed -n '1,120p' generated/child.yml
  artifacts:
    name: "generated-child-$CI_PIPELINE_ID-$CI_JOB_ID"
    expire_in: 1 day
    paths:
      - generated/child.yml

run-generated-api:
  stage: children
  trigger:
    include:
      - artifact: generated/child.yml
        job: generate-api-child
    strategy: mirror

The printed YAML contains no secret. In a production generator, prefer a dedicated validation step and preserve a digest of the generated artifact. The trigger artifact path uses GitLab server path syntax; do not assume the runner operating system controls this path.

9. Dynamic configuration constraints to observe

  • The generated artifact must fit instance artifact/configuration limits.
  • The dynamic child’s include section cannot interpolate CI/CD variables.
  • The generator job runs on a runner; the trigger job does not run a script.
  • Generated configuration is compiled after the generator succeeds, so syntax or semantic errors are child-creation failures, not generator-runtime success.
  • Keep generation deterministic for the same validated metadata and source SHA.

10. Challenge: which layer should solve each requirement?

Requirement Correct layer Why
Run API checks only when API files changed Parent trigger job rules:changes Controls whether the child is created
Select a fixed safe child shape from known metadata Validated generator Creates bounded configuration from allowlisted inputs
Make parent wait for child outcome trigger:strategy: mirror Controls status coupling
Pass a non-secret area name Trigger input/explicit variable Makes the child contract visible
Give child a deployment credential Secret/identity mechanism in authorized child job Do not forward broad parent secrets
Fetch parent artifact in child Optional cross-pipeline artifact mechanism Separate dataflow from orchestration; paid feature may apply

11. Cleanup

Export only the evidence you need, then delete the disposable branch/project resources you created. Do not delete unrelated pipelines or artifacts by ambiguous “latest” selection. If the project is shared, remove only the lab files/branch after confirming the exact ref.

Knowledge check

Why does the trigger job not need a runner?

What should happen on an API-only commit in the static example?

Why is AREA validated against an allowlist before generating YAML?

What should you compare between the parent and dynamic child?

Does the mandatory lab require needs:pipeline:job?

Next lesson

Configuration, design choices, and tradeoffs

Choose pipeline boundaries, static vs dynamic configuration, status strategy, forwarding, and bounded fan-out deliberately.

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.

Current behavior used by this chapter: parent-child pipelines are available on Free/Premium/Ultimate and GitLab.com/Self-Managed/Dedicated. Child pipelines run in the same project, ref, and commit SHA as the parent and report 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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.