Checkpoint Lab — CI/CD Variables, Predefined Variables, File Variables, Masking, Protection, Expansion, and Precedence
The checkpoint builds a controlled precedence matrix using only synthetic values. You will predict the winner before each run, prove the effective value in several contexts, diagnose one protection failure and one expansion/typing failure, collect evidence without leaking secrets, and clean up only the variables and branches created by the lab.
Learning objectives
- Construct a small precedence matrix with synthetic values and predict the winner before each pipeline run.
- Prove results across YAML-only, project-variable, optional pipeline-variable, protected/unprotected, and file-variable contexts.
- Inject one deliberate protection failure and one expansion/typing failure, then repair only the causal configuration.
- Produce an evidence packet containing exact source/ref/SHA, pipeline/job IDs, variable provenance/type/protection metadata, effective non-secret values, and assumptions.
- Remove only the disposable variables/branches created for Chapter 06 and bridge the operating model into Chapter 07 secrets management.
1. Checkpoint goal and evidence contract
Build a small variable matrix that answers four questions with evidence:
- Which YAML value wins when only YAML sources exist?
- What changes when the same key exists as a project variable?
- What changes when a higher-precedence pipeline value is allowed?
- How do protected and file variables differ from ordinary values?
Before each run, write your expected winner and expected protected/file state. After the run, capture the exact pipeline/job IDs, source/ref/SHA, runner ID/executor, and only the synthetic effective values or presence/hash metadata.
2. Preflight and assumptions
- Disposable GitLab project; no proprietary source or production settings.
- GitLab Free path is sufficient for project variables, file variables, pipelines, and protected branches when you have the required role.
- Any runner capable of executing the simple shell commands is acceptable; record Runner version/executor if visible.
- No real secrets; no cloud/Kubernetes/registry side effects.
- Pipeline-variable row is optional if the project correctly restricts that capability; do not weaken a non-lab policy.
LAB_BRANCH="ch06/checkpoint"
PROTECTED_BRANCH="ch06/checkpoint-protected"
EVIDENCE_DIR="ch06-checkpoint-evidence"
mkdir -p "$EVIDENCE_DIR"
git status --short --branch
git rev-parse HEAD | tee "$EVIDENCE_DIR/head-before.txt"
3. Use one deterministic checkpoint configuration
stages: [inspect]
variables:
LAB_WINNER: "yaml-default"
BASE_FRAGMENT: "green"
EXPANDED_FRAGMENT: "$BASE_FRAGMENT-release"
LITERAL_FRAGMENT:
value: "$BASE_FRAGMENT-release"
expand: false
checkpoint_probe:
stage: inspect
variables:
LAB_WINNER: "yaml-job"
script:
- printf 'source=%s\n' "$CI_PIPELINE_SOURCE"
- printf 'pipeline=%s job=%s\n' "$CI_PIPELINE_ID" "$CI_JOB_ID"
- printf 'ref=%s sha=%s\n' "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA"
- printf 'runner=%s executor_evidence=job-metadata\n' "${CI_RUNNER_ID:-unknown}"
- printf 'winner=%s\n' "$LAB_WINNER"
- printf 'expanded=%s\n' "$EXPANDED_FRAGMENT"
- printf 'literal=%s\n' "$LITERAL_FRAGMENT"
- if test -n "${LAB_PROTECTED_FLAG:-}"; then echo 'protected_present=yes'; else echo 'protected_present=no'; fi
- if test -n "${LAB_CONFIG_FILE:-}"; then test -f "$LAB_CONFIG_FILE"; printf 'file_bytes=%s\n' "$(wc -c < "$LAB_CONFIG_FILE")"; sha256sum "$LAB_CONFIG_FILE" | sed 's/ .*/ <LAB_CONFIG_FILE>/'; else echo 'file_variable=absent'; fi
4. Prediction matrix: write this before executing
| Run | Variable sources | Expected winner | Protected flag | File variable |
|---|---|---|---|---|
| A | YAML default + YAML job | yaml-job |
Absent unless separately configured. | Absent unless separately configured. |
| B |
Run A + project LAB_WINNER=project-setting
|
project-setting |
Same as context. | Same as context. |
| C (optional) |
Run B + allowed pipeline
LAB_WINNER=manual-pipeline
|
manual-pipeline |
Same as ref context. | Same. |
| D | Project protected flag on unprotected branch | Project winner | no |
Present if configured. |
| E | Same config on exact protected lab branch | Project winner | yes |
Present if configured. |
Also predict expanded=green-release and
literal=$BASE_FRAGMENT-release for every run unless you
deliberately inject the expansion failure later.
5. Run A: YAML-only baseline
Ensure none of the four LAB_ project variables exist
yet. Commit/push the checkpoint branch and preserve the SHA:
git switch -c "$LAB_BRANCH"
git add -- .gitlab-ci.yml
git diff --cached --check
git commit -m "ch06: checkpoint variable matrix"
CHECKPOINT_SHA="$(git rev-parse HEAD)"
printf '%s\n' "$CHECKPOINT_SHA" | tee "$EVIDENCE_DIR/checkpoint-sha.txt"
git push -u origin "$LAB_BRANCH"
Expected: winner=yaml-job. Record pipeline/job
identifiers and compare the job SHA with
CHECKPOINT_SHA.
6. Run B: project variable overrides YAML
Create project variable LAB_WINNER=project-setting.
Rerun on the same ref/SHA when possible or record the new SHA if
configuration changed. Expected:
winner=project-setting.
This run is the minimum proof that variable source precedence changes behavior without changing the job definition. Preserve both A and B job URLs/IDs.
7. Run C (optional): pipeline variable overrides project variable
If the disposable project permits pipeline variables for your role,
start a manual pipeline with
LAB_WINNER=manual-pipeline. Expected: the pipeline
value wins. Record the pipeline source (normally
web for a Run pipeline UI action), ref/SHA, and
variable-setting context.
If pipeline variables are disabled, record not executed due to project security setting and retain the current documented expected precedence. Do not lower an organization security setting to manufacture a green lab.
8. Add file and protected variables, then prove access boundaries
Create:
-
LAB_CONFIG_FILE— File type, fake contentmode=checkpoint source=synthetic. -
LAB_PROTECTED_FLAG— ordinary variable, Protect variable enabled, arbitrary synthetic value.
Run on the unprotected checkpoint branch. Expected: file
metadata/hash is present; protected flag is absent. Then create and
protect the exact ch06/checkpoint-protected branch in
the disposable project and run the same SHA/configuration there
where practical. Expected: protected_present=yes.
Never print either raw variable content. The checkpoint proves interface and authorization, not value disclosure.
9. Failure injection 1: break expansion/typing deliberately
Choose one safe failure:
-
Expansion failure: change
EXPANDED_FRAGMENTto the object form withexpand: falsewhile still expectinggreen-release. Observe the literal value, diagnose the YAML expansion setting, then restore it. -
Typing failure: add
LAB_NUMERIC_ID: 012345unquoted, observe the parsed value in a synthetic-only job, then repair to"012345".
Preserve the failing pipeline/job ID before the repair. Do not combine both failures in one run; one causal change per experiment keeps evidence interpretable.
10. Failure injection 2: treat absence as a diagnostic, not a reason to weaken protection
On the unprotected branch, the protected flag is deliberately absent. Write a hypothetical consumer that requires it and fails closed:
require_protected_flag:
script:
- test -n "${LAB_PROTECTED_FLAG:-}" || { echo 'required protected capability unavailable'; exit 3; }
- echo 'protected capability present (value not printed)'
Run it on the unprotected lab branch and preserve the failure. The repair is to run that capability from the protected lab branch (or redesign the workflow), not to clear the variable’s protection.
11. Verification checklist
- Every executed row records project/path, pipeline source, ref, SHA, pipeline ID, job ID, and runner ID/description where visible.
- Run A proves job YAML beats top-level YAML.
- Run B proves project setting beats YAML.
- Run C either proves allowed pipeline-variable precedence or documents why the security setting prevented the experiment.
- File variable is proven by temporary-file existence, byte count, and hash—never content.
- Protected variable is proven by presence/absence across controlled ref contexts—never content.
- One expansion/typing failure and one protection failure retain their first-failure evidence and causal repairs.
12. Exact cleanup and rollback
Before deleting anything, list the exact lab resources in your notes. Then:
-
Delete only project variables
LAB_WINNER,LAB_CONFIG_FILE, andLAB_PROTECTED_FLAG(plusLAB_MASKED_DEMOif you created it earlier). -
Remove only the exact protection rule for
ch06/checkpoint-protectedif it was created solely for this lab. - Delete only the two Chapter 06 lab branches after confirming their names and remote SHAs.
- Restore any disposable project pipeline-variable setting you changed; preferably do not change it at all.
- Keep non-secret evidence/notes; do not retain temporary variable files or secret-like raw values in artifacts.
13. Required evidence packet
Project/path, pipeline source, ref, immutable SHA, pipeline ID.
Job ID/name/status and first-failure job IDs before repair.
Runner ID/description/version/executor where visible; enough to show execution context.
For each LAB_ key:
source/scope/type/protection/visibility/expansion metadata,
never real sensitive value.
Only synthetic LAB_WINNER/expansion values and
boolean presence for protected data.
Temporary file path existence, byte count, hash, and type metadata; no raw content.
Expansion/typing failure plus protected-availability failure, each with causal repair.
Offering/tier, role, pipeline-variable restriction, protected-branch permissions, runner availability.
Proof that only the disposable variables/protection rules/branches were removed.
14. What Chapter 06 adds to the operating model
You can now treat CI/CD variables as governed data rather than magical environment strings: identify their provenance, availability phase, type, visibility/protection, expansion mechanism, and precedence; prove the effective non-secret value; and diagnose failures without dumping secrets.
Chapter 07 moves from variable mechanics into secrets management: external providers, protected data, rotation, and least-privilege retrieval. The key bridge is that masking/protection are useful controls but are not a complete secret-lifecycle strategy.
Knowledge check
What is the minimum proof that project variables outrank YAML in the checkpoint?
Two correlated runs on known source/ref/SHA context: Run A prints yaml-job with no project variable; Run B prints project-setting after adding only the project variable.
Why does the file-variable evidence packet contain a hash instead of content?
The learning goal is to prove file materialization and identity without teaching secret disclosure in logs.
If Run C cannot set a pipeline variable because the project forbids it, is the checkpoint incomplete?
No. Document the security restriction and current expected precedence; do not weaken a non-lab security baseline.
What is the safe repair for a job that requires a protected variable but runs on an unprotected branch?
Move the capability to the governed protected ref or redesign the workflow; do not simply unprotect the variable.
What concept from this chapter is most important before Chapter 07?
A variable’s masking/protection does not make it a complete secret-management solution; code/ref/runner/identity and secret lifecycle still matter.
Official references and version notes
- GitLab CI/CD variables — project/group/instance variables, visibility, masking, hiding, protection, file variables, expansion, precedence, pipeline-variable restrictions, and security guidance.
- Predefined CI/CD variables reference — current pre-pipeline, pipeline, and job-only availability phases and variable definitions.
- Where variables can be used — GitLab, GitLab Runner, and execution-shell expansion mechanisms and keyword-specific behavior.
-
CI/CD YAML syntax reference
—
variables, job variables,variables:expand,rules:variables,workflow:rules:variables, and inheritance semantics. - CI/CD inputs — typed/configuration inputs, interpolation, and the current recommendation to prefer inputs over ad-hoc pipeline variables for newer parameterized pipelines where applicable.
- Protected branches — current protected-ref behavior that underpins protected-variable availability.
Variable behavior in this chapter was rechecked against current primary GitLab documentation on 2026-09-11. The current documentation orders policy variables above manual-job and pipeline variables, then project/group/instance values, dotenv values, YAML variables, deployment variables, and most predefined variables. It also documents project/group variable visibility as Masked by default since GitLab 18.3, UI variable-reference expansion disabled by default since GitLab 18.6, and pipeline inputs as the preferred parameter mechanism over pipeline variables for newer designs where applicable. These are version-sensitive details: verify them again before publishing or automating production 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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.