Checkpoint Lab — YAML Reuse with include, extends, Anchors, !reference, Hidden Jobs, and Template Architecture
The checkpoint lab refactors a duplicated pipeline using two reuse mechanisms, proves the intended compiled jobs are equivalent, deliberately breaks one inheritance edge, repairs it from merged-configuration evidence, and records the exact include/ref/override state required for safe reuse.
Learning objectives
- Refactor a duplicated pipeline using both extends and a cross-file reuse mechanism.
- Prove the intended compiled jobs are equivalent using expanded configuration rather than visual similarity of source files.
- Inject and repair an inheritance failure that replaces an array unexpectedly.
- Produce an evidence packet covering include source/ref/integrity, merged jobs, overrides, pipeline identity, and assumptions.
- Document the controls required before converting local templates into organization-wide components in Chapter 13.
1. Checkpoint scenario and acceptance criteria
You inherit a pipeline with three nearly identical validation jobs.
Refactor it so duplication is reduced using (1) a local included
hidden job plus extends, and (2) a reusable rules array
selected with !reference. Prove from expanded
configuration that all intended final jobs remain equivalent in
image/stage/rules/setup, while job-specific scripts remain explicit.
Then deliberately break one inheritance edge by overriding an array, preserve the failed evidence, repair it without hiding the root cause, and document how the same template would be pinned if moved to a shared project.
2. Preflight and assumptions
-
Disposable project
glci-ch12-checkpointand branchglci/ch12-checkpoint. - GitLab Free-compatible core syntax; current GitLab behavior verified 2026-09-11.
- Any ordinary trusted runner; no privileged execution, cloud account, registry, or production credentials.
- CI Lint/pipeline editor available for merged/expanded configuration inspection.
- If no runner exists, completing compilation/expanded-YAML evidence plus local shell simulation is an accepted faithful path; label runtime fields “not observed.”
3. Predict state changes before editing
| Prediction | How to verify independently |
|---|---|
| Local include becomes part of compiled config at same source SHA | Expanded YAML shows hidden template and inherited final jobs |
| Final jobs preserve image/stage/setup while scripts stay job-specific | Compare expanded jobs key-by-key |
| Rules from !reference appear concretely in final jobs | Expanded YAML shows resolved rules array |
| Deliberate child before_script override removes parent array | Expanded YAML proves replacement before job execution |
| Repair restores intended setup without changing unrelated jobs | Diff expanded config + run only disposable pipeline |
4. Create the reusable local template
# ci/checkpoint.yml
.validation_base:
stage: test
image: alpine:3.20.3
before_script:
- echo "prepare:$CI_COMMIT_SHA"
- mkdir -p evidence
variables:
CH12_CONTRACT: "v1"
.validation_rules:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_PIPELINE_SOURCE == "push"'
The hidden job is the hash-like job template. The separate hidden rules object is selected explicitly rather than relying on array merging.
5. Build the root configuration
include:
- local: /ci/checkpoint.yml
stages: [test]
lint_config:
extends: .validation_base
rules: !reference [.validation_rules, rules]
script:
- echo "lint:$CH12_CONTRACT" | tee evidence/lint.txt
artifacts:
when: always
paths: [evidence/]
expire_in: 1 day
unit_config:
extends: .validation_base
rules: !reference [.validation_rules, rules]
script:
- echo "unit:$CH12_CONTRACT" | tee evidence/unit.txt
artifacts:
when: always
paths: [evidence/]
expire_in: 1 day
integration_config:
extends: .validation_base
rules: !reference [.validation_rules, rules]
script:
- echo "integration:$CH12_CONTRACT" | tee evidence/integration.txt
artifacts:
when: always
paths: [evidence/]
expire_in: 1 day
6. Validate and capture the compiled contract before execution
Run CI Lint and inspect the expanded configuration. Save a text/PDF/screenshot-equivalent evidence note containing:
- source SHA and branch,
- root include path,
- the fully expanded keys for all three jobs,
- resolved rules, image, before_script, variables, script, and artifacts,
- the validation result and timestamp.
If your GitLab provides pipeline simulation, use it to confirm expected jobs for a push. Validation success proves configuration shape, not runner execution or artifact upload.
7. Run once and capture runtime evidence
Push the disposable branch. Record pipeline source/ref/SHA, pipeline ID, each job ID/status, runner ID/version/executor, and the three small evidence artifacts. Do not store secrets in the artifacts.
printf 'source=%s
ref=%s
sha=%s
pipeline=%s
job=%s
runner=%s
' "$CI_PIPELINE_SOURCE" "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_JOB_ID" "${CI_RUNNER_ID:-unknown}"
8. Deliberately break one inheritance edge
Change only unit_config:
unit_config:
extends: .validation_base
rules: !reference [.validation_rules, rules]
before_script:
- echo "unit-only-setup"
script:
- test -d evidence
- echo "unit:$CH12_CONTRACT" | tee evidence/unit.txt
Predict the result before running: the child
before_script replaces the parent array, so
evidence/ is not created and the first
test -d evidence fails. Preserve the expanded job
showing the replacement and the first failing job trace.
9. Repair the causal configuration, not the symptom
Do not add mkdir -p evidence into the job’s
script merely to make it pass; that would hide the
inheritance misunderstanding. Remove the child
before_script override, or define a deliberately
reusable setup array and reference it explicitly. Then create a new
pipeline and prove the expanded configuration again.
10. Model the shared-project promotion path
If ci/checkpoint.yml later moves to a shared project,
document this reviewed consumer form:
include:
- project: 'platform/ci-library'
ref: 'fedcba9876543210fedcba9876543210fedcba98'
file: '/templates/validation.yml'
The exact project path and SHA become part of the reusable dependency evidence. Chapter 13 will turn this idea into a versioned component/interface model with explicit inputs.
11. Required evidence packet
| Evidence | Minimum content |
|---|---|
| Source identity | Project, branch/ref, exact source SHA, pipeline source |
| Configuration dependency | Root file + local include path; simulated future project path/full SHA |
| Compiled configuration | Expanded lint/unit/integration jobs before, broken, and repaired states |
| Pipeline/job records | Pipeline ID, job IDs/statuses, runner/executor if observed |
| Artifacts | Small non-secret evidence files and expiry |
| Failure record | First broken unit_config trace + expanded before_script replacement |
| Assumptions/limits | GitLab offering/version, no privileged/paid/cloud dependency, runtime-not-observed if simulation only |
12. Verification checklist and cleanup
- Exactly three intended jobs exist for the chosen pipeline source.
- All three expanded jobs have the same intended image/stage/setup/contract variable.
- Each job’s script remains explicit and unique.
- The deliberate failure is explained by array replacement, not runner state.
- The repaired pipeline restores the parent setup and succeeds without unrelated changes.
- No real token, secret, privileged runner, production URL, registry, or external deployment appears anywhere.
After saving the evidence packet, delete only the disposable branch/project created for this lab.
13. What Chapter 12 adds to the production operating model
You can now treat shared YAML as executable, versioned operational code: its provenance is explicit, its merge contract is inspectable, and failures are diagnosed from the compiled configuration. Chapter 13 builds on this foundation with CI/CD components, the Component Catalog, typed inputs, versioned reuse, and organization-wide building blocks.
Knowledge check
The broken unit job fails because evidence/ is missing. Which layer is causal?
The configuration inheritance layer: the child before_script replaced the parent before_script that created the directory.
Why is adding mkdir to script a weak repair?
It hides the incorrect inheritance assumption and duplicates setup. The better repair restores or explicitly references the intended setup contract.
What proves the refactor preserved intended configuration before any runner executes?
The expanded/merged configuration shows equivalent effective image, stage, setup, variables, rules, and job-specific scripts.
Which identity must be added to the evidence packet if the local include becomes include:project?
The shared project path, exact included file path, and immutable full commit SHA (or other explicitly governed version identity).
Why bridge next to CI/CD components rather than keep growing hidden-job conventions forever?
Components provide a stronger versioned reusable interface with explicit inputs and distribution semantics, reducing caller dependence on template internals.
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. CI reuse and include behavior evolves across GitLab releases, especially include subkeys, integrity/caching, inputs/components, and template deprecations. Re-check the deployed GitLab version before relying on newer include features on Self-Managed or Dedicated installations.
-
CI/CD YAML syntax reference
— current
includetypes/subkeys, merge behavior,extends, hidden jobs, defaults, and pipeline syntax. - Use CI/CD configuration from other files — local/project/remote/template include behavior, variables, rules, overrides, troubleshooting, and trust guidance.
-
Optimize GitLab CI/CD configuration files
— anchors, hidden jobs,
extends, reverse deep merge, and!reference. -
Pipeline editor
— validation and expanded configuration showing includes, anchors,
extends, and!referenceresolved. - CI Lint — validate and simulate configuration before creating a real pipeline.
- CI/CD inputs — typed configuration interfaces that become increasingly important in Chapter 13.
integrity with a
base64 SHA-256. YAML anchors cannot cross files.
extends reverse-deep-merges hashes but array values are
replaced, not concatenated. GitLab supports up to eleven inheritance
levels but recommends avoiding deep inheritance;
!reference can select configuration from included files
and has parsing constraints with CI/CD inputs.
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.