Chapter 12Lesson 04~160 minutes

YAML Reuse with include, extends, Anchors, !reference, Hidden Jobs, and Template Architecture: Diagnostics, Failure Modes, Security, and Performance

Reuse failures are often invisible in the source fragment you are reading because the effective behavior exists only after GitLab resolves includes and inheritance. This lesson diagnoses moving includes, array replacement, excessive inheritance, access failures, integrity failures, and unreviewed template trust from the merged configuration outward.

YAML reuseincludeextends!referenceTemplate trust

Learning objectives

  • Diagnose unexpected behavior by preserving the original pipeline/configuration snapshot and inspecting merged YAML first.
  • Recognize moving remote/project includes, access failures, integrity mismatches, array replacement, and over-deep inheritance.
  • Separate include-resolution failures from job-rule, runner, script, artifact, and deployment failures.
  • Repair the smallest causal configuration edge without weakening access controls or replacing immutable references with moving branches.
  • Preserve pipeline/job IDs, source SHA, include identity, and first-failure evidence before retrying.

1. Evidence-first diagnostic sequence for reuse failures

  1. Preserve pipeline ID, source SHA, first validation error/job trace, and the current expanded configuration if a pipeline exists.
  2. List every include source/type/ref/integrity value and determine whether resolution succeeded.
  3. Inspect the merged job and its extends/!reference/anchor chain.
  4. Confirm workflow/job rules only after configuration compilation is proven.
  5. Only then inspect runner/executor, scripts, artifacts, deployments, or external systems.
  6. Apply the smallest configuration correction and create a new pipeline when compilation inputs changed.

This ordering prevents a common waste: debugging a runner for a job definition that GitLab never compiled as intended.

2. Failure: a moving project/remote include changed unexpectedly

Symptom: yesterday’s pipeline and today’s pipeline use the same root-file text and source commit, but effective jobs differ. First compare pipeline IDs and expanded YAML. If the include used ref: main or an unpinned remote URL, the new pipeline may have fetched new template content.

Repair by selecting a reviewed immutable project SHA or remote integrity digest. Do not “fix” the consuming script to accommodate unknown upstream drift before establishing what changed.

3. Failure: inheritance replaces an array unexpectedly

Consider this valid configuration:

.base:
  before_script:
    - echo "install trusted tooling"
  script:
    - echo "base test"

child:
  extends: .base
  before_script:
    - echo "child setup"
  script:
    - echo "child test"

The child arrays replace the parent arrays. The configuration can be syntactically valid and still violate your intent. The fix is architectural: either make replacement explicit and self-contained, or reuse the needed array with !reference/an anchor in a suitable scope.

4. Failure: include resolution says not found or access denied

For include:project, the user creating the pipeline must be able to read the included private project. A permission failure occurs before runner execution. Preserve the exact project path/ref/file and the identity context; do not replace the private project include with a public remote URL as a shortcut.

For include:remote, remember there is no authenticated fetch. Private remote resources therefore cannot be solved by adding a job token to a later script—the failure happens earlier during configuration resolution.

5. Failure: remote integrity mismatch

If include:integrity does not match fetched content, GitLab rejects the remote include. That is a safety signal. Preserve the expected digest and upstream response identity, then review whether the upstream file legitimately changed. If yes, review the new content and intentionally update the digest; if not, investigate the upstream change. Never remove integrity merely to make the pipeline green.

6. Failure: include loop or excessive graph

Current GitLab defaults to at most 150 includes per pipeline, including nested includes. A recursive pair of files can consume this budget quickly. The correct fix is to simplify the graph and remove the loop, not to increase an instance-wide include limit as the first response.

Use the pipeline editor/CI Lint to narrow the include chain and document which file introduced the recursion.

7. Failure: behavior is correct but no one can explain why

Deep extends chains are an operational failure even when the pipeline is green if reviewers cannot determine the effective job quickly. Measure review burden as part of maintainability. Flatten inheritance, rename hidden jobs semantically, and publish the caller contract.

8. Failure: shared template adds an unexpected privileged action

A central template adds a deployment or privileged image-building step. The consumer’s pipeline compiles successfully, and the step can reach protected credentials. This is a trust/governance failure, not a YAML syntax failure.

Response: preserve the template revision and compiled job, disable/limit the privilege at the consumer boundary if needed, revert/pin to the known-good template version, and review the template-owner change process. Do not print credentials or weaken protections for troubleshooting.

9. Causal map: stop at the first broken layer

flowchart TD A[Root SHA] --> B[Include resolution] B --> C[Merge / extends / !reference] C --> D[Workflow + job rules] D --> E[Job graph] E --> F[Runner] F --> G[Script/tools] G --> H[Artifacts/deployment] B -. fail .-> X[Fix source/ref/access/integrity] C -. fail .-> Y[Fix merge/override/inheritance] F -. fail .-> Z[Only now debug runner]

Preserve evidence at the failed layer before retrying because a new pipeline can resolve different moving dependencies.

10. Performance: include graph size matters, but correctness comes first

Include resolution has a finite time budget and remote fetches can be rate-limited or unavailable. Keep the graph bounded and avoid unnecessary nested dependencies. Newer GitLab versions can support caching for remote includes, but freshness/caching behavior is version-sensitive and should not replace immutable identity/integrity.

Optimization follows measurement: count includes, identify remote/network dependencies, and measure pipeline creation latency before reorganizing templates.

11. Compact recovery playbook

Evidence Likely layer Least-destructive action
Include not found/access denied Resolution/authorization Correct project/ref/file or access; keep private dependency private
Integrity mismatch Remote dependency identity Review upstream bytes; update digest only after review
Expanded job missing parent script Merge/inheritance Fix array replacement/reference design
Same root SHA, different new pipeline config Moving include Pin reviewed immutable revision
150-include error Graph recursion/size Break loop/simplify graph
Job correct but impossible to review Maintainability/governance Flatten inheritance and document contract

Knowledge check

A pipeline cannot resolve a private include:project. Should you debug the runner first?

Why is deleting integrity a poor response to a remote include mismatch?

Two new pipelines from the same root source SHA differ. What reuse evidence should you compare first?

What is the causal layer when a child script replaces a parent script?

When should increasing the 150-include limit be considered?

Next lesson

Checkpoint lab

Refactor duplicated jobs, prove compiled equivalence, inject an inheritance failure, repair it causally, and package reusable-CI evidence.

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.

Current behavior used by this chapter: includes are resolved first and the root configuration is merged afterward; all include resolution has a 30-second time limit and the default nested-include limit is 150. A project include can pin a full 40-character commit SHA. Remote includes are public HTTP(S) GETs without authentication and can use 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.

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