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.
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.
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?
The fully expanded/merged GitLab CI configuration, not just the source files.
Why does the local include lab not need a Catalog project?
Catalog publication is a distribution/discovery lifecycle. Local includes and typed input files work without publication.
What proves that the refactor did not change the source revision executed by jobs?
Compare job CI_COMMIT_SHA/pipeline SHA with the
local commit SHA recorded before execution.
A shared project include uses ref: main. What
production concern remains?
The dependency is mutable: a later commit to
main can change consumer behavior without a
consumer commit.
Should a component be given broad secrets because many consumers may need them?
No. Components should receive only the minimum credentials required for their documented behavior, scoped by the consuming project and trust boundary.
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:
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.