Chapter 20Lesson 02~320 minutes

Includes, Templates, CI/CD Components, Component Catalog, and Reusable Pipeline Architecture: Guided Hands-On Workflow and Core Operations

Refactor repeated YAML into a local include, validate the expanded configuration, exercise a component-style typed-input contract, and prove immutable dependency identity without paid services.

Hands-onLocal includeCI LintInputsImmutable pinProvenance

Learning objectives

  • Extract repeated job behavior into a same-commit local include.
  • Use CI Lint to capture expanded configuration before execution.
  • Exercise a typed input contract without requiring Catalog publication.
  • Record an immutable cross-project/component dependency pattern.
  • Verify runtime SHA and clean up disposable resources safely.
Availability baseline (verified 2026-08-22 against current GitLab 19.3 documentation). include, CI/CD inputs, CI/CD components, the CI/CD Catalog, and CI Lint are available on Free/Premium/Ultimate across GitLab.com, Self-Managed, and Dedicated. Component references must resolve on the same GitLab instance as the consuming project. The mandatory labs require only a disposable Free project and local configuration. Catalog publication is optional because publishing requires a catalog-enabled component project, appropriate project roles, a semantic-version release, and repository metadata. The current glab repo publish catalog command is experimental and therefore is not a mandatory production path.

1. Lab scenario and safety contract

You own a disposable Free project containing two nearly identical verification jobs. You will extract their common behavior into a local include, prove the expanded configuration before running it, add a typed reusable configuration fixture, and record what an immutable external dependency would look like. Nothing deploys, contacts production, or needs secrets.

2. Preflight: inspect before creating files

git status --short
git rev-parse HEAD
git remote -v

PROJECT_ID="12345678"
glab api "projects/$PROJECT_ID" --jq '{id,path_with_namespace,default_branch,visibility}'
glab api "projects/$PROJECT_ID/pipelines?per_page=5" --jq '.[] | {id,ref,sha,status,source}'

Use your disposable project ID. If the branch already contains shared CI experiments, create a new branch instead of overwriting them.

3. Start with deliberately duplicated configuration

stages: [test]

lint_api:
  stage: test
  script:
    - echo "lint api"
    - test -f src/api.txt

lint_worker:
  stage: test
  script:
    - echo "lint worker"
    - test -f src/worker.txt

Create harmless files src/api.txt and src/worker.txt. The duplication is intentional evidence for the refactor.

4. Extract the reusable fragment into a local include

Create .gitlab/ci/ch20-verify.yml:

.verify_template:
  stage: test
  before_script:
    - printf "job=%s sha=%s\n" "$CI_JOB_NAME" "$CI_COMMIT_SHA"
  script:
    - test -f "$TARGET_FILE"

Then replace the duplicate body in .gitlab-ci.yml:

stages: [test]

include:
  - local: .gitlab/ci/ch20-verify.yml

lint_api:
  extends: .verify_template
  variables:
    TARGET_FILE: src/api.txt

lint_worker:
  extends: .verify_template
  variables:
    TARGET_FILE: src/worker.txt

The include owns shared implementation; each job still owns its explicit target. Local include resolution stays tied to the same commit as the consumer.

5. Inspect the fully merged configuration before execution

PROJECT_ID="12345678"

jq --null-input --arg yaml "$(cat .gitlab-ci.yml)" '{content:$yaml}' \
| glab api --method POST "projects/$PROJECT_ID/ci/lint?include_merged_yaml=true" \
    --header "Content-Type: application/json" \
    --input - \
    --jq '{valid,errors,warnings,merged_yaml}'

Expected: valid is true and the merged YAML contains the template plus both extending jobs. Save the response as evidence if you need to compare before/after behavior.

6. Add a component-style typed-input fixture without publishing anything

Create .gitlab/ci/ch20-greeting.yml:

spec:
  inputs:
    prefix:
      description: "Job-name prefix"
      default: lab
      regex: '^[a-z0-9-]+$'
    strict:
      type: boolean
      default: true
---
"$[[ inputs.prefix ]]-contract-check":
  stage: test
  script:
    - printf 'strict=%s\n' '$[[ inputs.strict ]]'

Consume it with an ordinary local include and explicit inputs:

include:
  - local: .gitlab/ci/ch20-verify.yml
  - local: .gitlab/ci/ch20-greeting.yml
    inputs:
      prefix: reusable
      strict: true

This proves the typed contract mechanics used by components without requiring a second project or Catalog publication.

7. Commit only after lint passes, then inspect causal evidence

git switch -c ch20/reuse-lab
git add .gitlab-ci.yml .gitlab/ci/ch20-verify.yml .gitlab/ci/ch20-greeting.yml src/
git commit -m "lab: extract reusable CI configuration"
git push -u origin ch20/reuse-lab

SHA=$(git rev-parse HEAD)
echo "$SHA"

In GitLab, inspect the pipeline graph and each job log. Every job must report the same commit SHA you recorded locally. The reusable contract changes configuration only; it does not create a new runtime identity.

8. Record how a cross-project dependency would be pinned

Do not create a shared project merely to prove syntax. Record the reviewed identity in a fixture instead:

include:
  - project: platform/ci-library
    ref: 3f4c18c6b2d7e9a1f25bf6dcde2bdb3b483b6f1a
    file: /templates/verify.yml
    inputs:
      strict: true

The full SHA is synthetic. In production, it must be the exact reviewed commit from the real library project. A branch such as main would be easier to maintain but weaker as a reproducibility boundary.

9. Optional: turn the fixture into a disposable component project

If you want the full component experience, create a second disposable project on the same GitLab instance, add README.md and templates/verify.yml, and consume it at an exact commit SHA. Do not publish to the Catalog for the mandatory lab.

include:
  - component: $CI_SERVER_FQDN/lab/ch20-components/verify@0123456789abcdef0123456789abcdef01234567
    inputs:
      stage: test

The SHA above is documentation syntax only. Resolve and record the actual commit in your disposable component project before use.

10. Optional Catalog publication simulation

Current Catalog publication requires the project to be marked as a Catalog project, a description, root README, component templates, and a semantic-version release created by a CI job using the release keyword. Enabling catalog-project status requires Owner; publishing requires Maintainer or Owner. Because these are governance changes, the mandatory path stops at a local simulation.

release_component:
  stage: release
  rules:
    - if: $CI_COMMIT_TAG
  script:
    - echo "release metadata only"
  release:
    tag_name: $CI_COMMIT_TAG
    description: "Release $CI_COMMIT_TAG"

Do not use this in a valuable project merely to complete the lesson.

11. Challenge: pick the right reuse surface

Choose a mechanism for each case and justify it:

Case Best starting point Why
Three jobs in one repository share setup YAML anchor/extends or local include Keep ownership and version coupled to one repository.
Twenty repositories need one reviewed quality job Component or SHA-pinned project include Central ownership and explicit consumer dependency.
Third-party HTTPS snippet must be consumed Prefer a reviewed same-instance project/component; if remote is unavoidable, use integrity Reduce mutable external trust.
A platform team wants discoverability and semantic releases CI/CD component + Catalog Catalog adds discovery and release lifecycle.

12. Verification and cleanup

git show --stat HEAD
git diff HEAD^..HEAD -- .gitlab-ci.yml .gitlab/ci/

# After evidence capture, remove only the synthetic lab branch if desired.
git switch main
git branch -D ch20/reuse-lab 2>/dev/null || true
# Remote branch deletion is optional and destructive; verify the exact branch first.
git ls-remote --heads origin ch20/reuse-lab

If you created an optional component project, delete it only if it is truly disposable and only after recording the component SHA and consumer evidence.

Knowledge check

What should you inspect before running a newly refactored reusable pipeline?

Why does the local include lab not need a Catalog project?

What proves that the refactor did not change the source revision executed by jobs?

A shared project include uses ref: main. What production concern remains?

Should a component be given broad secrets because many consumers may need them?

Summary

You refactored duplication into a same-commit include, inspected merged YAML, exercised typed input validation, and documented immutable cross-project/component identity. Live Catalog publication is optional because it is governance work, not required to learn reusable architecture.

Official references

Primary sources used for the current GitLab 19.3 behavior taught in this lesson:

Next lesson

Choose the right abstraction and versioning contract

Lesson 3 compares anchors, extends, includes, components, local versus centralized ownership, immutable versus floating versions, and compatibility policy.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.