CI/CD Variables, Predefined Variables, File Variables, Masking, Protection, Expansion, and Precedence: Guided Hands-On Workflow and Core Operations
Turn the variable model into observable evidence with a disposable project. You will inspect a small allowlist of predefined variables, define synthetic project and YAML variables, prove which value wins, use a file variable without printing its content, compare protected and unprotected refs, and record the pipeline source/ref/SHA that explains each result.
Learning objectives
- Inspect only an allowlist of safe predefined variables instead of dumping the entire job environment.
- Prove YAML job/default precedence and then prove how a project variable can override both using a synthetic value.
- Use a project file variable as a temporary file path and verify it by metadata/hash without disclosing sensitive content.
- Compare protected-variable availability on protected and unprotected refs without printing the variable value.
- Record source/ref/SHA, pipeline/job IDs, runner/executor identity, variable provenance, and effective non-secret values for each experiment.
1. Scenario and stable lab keys
Create or reuse a disposable project dedicated to Chapter 06. Use the exact synthetic keys below so evidence and cleanup are unambiguous:
-
LAB_WINNER— precedence experiment; never sensitive. -
LAB_MASKED_DEMO— fake masked-value demonstration only. LAB_CONFIG_FILE— fake file-variable content.-
LAB_PROTECTED_FLAG— protected-variable presence test; never print its value.
The job configuration intentionally prints only these synthetic values plus a safe allowlist of predefined identifiers. It never enumerates the full environment.
2. Preflight: record source and settings before mutation
LAB_BRANCH="ch06/variables"
EVIDENCE_DIR="ch06-evidence"
mkdir -p "$EVIDENCE_DIR"
git status --short --branch
git rev-parse HEAD | tee "$EVIDENCE_DIR/local-head-before.txt"
git remote -v | tee "$EVIDENCE_DIR/remotes-before.txt"
# In GitLab UI, record only metadata for existing variables with these exact LAB_ keys.
# Do not export or screenshot values. If any LAB_ key already exists, choose a new prefix.
Also note whether the project currently restricts pipeline
variables. New GitLab.com projects can default to
no_one_allowed. Do not weaken an existing real project
simply to demonstrate precedence; this disposable project is the
only allowed mutation target.
3. Add the controlled variable probe
stages: [inspect]
variables:
LAB_WINNER: "yaml-default"
BASE_FRAGMENT: "blue"
EXPANDED_FRAGMENT: "$BASE_FRAGMENT-canary"
LITERAL_FRAGMENT:
value: "$BASE_FRAGMENT-canary"
expand: false
variable_probe:
stage: inspect
variables:
LAB_WINNER: "yaml-job"
script:
- printf 'source=%s\n' "$CI_PIPELINE_SOURCE"
- printf 'pipeline_id=%s job_id=%s\n' "$CI_PIPELINE_ID" "$CI_JOB_ID"
- printf 'ref=%s sha=%s\n' "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA"
- printf 'runner_id=%s\n' "${CI_RUNNER_ID:-unknown}"
- printf 'LAB_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
With no higher-precedence custom values,
LAB_WINNER should resolve to yaml-job,
because a job-level YAML variable outranks the top-level YAML
default. The expansion lines are deliberately non-secret so their
exact values can be observed safely.
4. Baseline run: prove YAML-only precedence
Commit the probe on the disposable branch and record the immutable SHA before pushing. Validate the CI configuration first using the Pipeline Editor/CI Lint.
git switch -c "$LAB_BRANCH"
git add -- .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "ch06: add variable provenance lab"
LAB_SHA="$(git rev-parse HEAD)"
printf '%s\n' "$LAB_SHA" | tee "$EVIDENCE_DIR/lab-sha.txt"
git push -u origin "$LAB_BRANCH"
Expected job evidence: LAB_WINNER=yaml-job,
expanded=blue-canary, and
literal=$BASE_FRAGMENT-canary. Record pipeline ID, job
ID, runner ID/description, source, ref, and SHA. If the job is
pending, diagnose runner eligibility from Chapters 04–05 rather than
changing variables.
5. Add a synthetic project variable and prove it outranks YAML
In Settings → CI/CD → Variables, create
LAB_WINNER with the synthetic value
project-setting. Keep it unprotected for this
precedence step and use ordinary variable type. Because the value is
non-sensitive, either visible or masked visibility is acceptable;
record the visibility metadata you selected.
Rerun the same commit/ref or create a small no-op commit. The job
should now print LAB_WINNER=project-setting. Nothing in
the YAML changed; only the higher-precedence project setting
changed. This is the central diagnostic lesson.
6. Add a file variable without exposing its content
Create project variable LAB_CONFIG_FILE with type
File and a fake value such as:
mode=training
source=synthetic
purpose=chapter06
Run the probe again. GitLab Runner materializes the value into a
temporary file and sets LAB_CONFIG_FILE to that path.
The job records only byte count and SHA-256. If you inspect locally
for learning, remember that a real sensitive file variable must not
be printed, uploaded as an artifact, or retained in a debug bundle.
7. Demonstrate masking safely with a fake value only
Create LAB_MASKED_DEMO with a synthetic value such as
MaskDemo_2026_Only and Masked visibility. Add a
temporary lab-only job:
mask_demo:
stage: inspect
script:
- printf 'exact=%s\n' "$LAB_MASKED_DEMO"
- printf 'presence=%s\n' "$(test -n "$LAB_MASKED_DEMO" && echo yes || echo no)"
The exact output should be redacted according to GitLab masking behavior, while the presence line reveals only a boolean. This is a demonstration of log behavior, not proof that the value is safe from malicious code. Remove the temporary job after recording the evidence.
8. Compare protected and unprotected refs without printing the value
Create LAB_PROTECTED_FLAG with any synthetic value and
enable Protect variable. The job already tests
presence without printing it.
-
Run on the unprotected
ch06/variablesbranch. Expected:protected_present=no. -
In the disposable project only, create a dedicated branch such as
ch06/protectedand protect that exact branch using the narrowest permissions appropriate to the lab. -
Run the same configuration on the protected branch. Expected:
protected_present=yes.
If your role cannot manage protected branches/variables, do not request elevated access merely for the course. Preserve the unprotected result and use a two-row expected-state simulation for the protected case.
9. Optional pipeline-variable precedence without weakening policy
If the disposable project already allows you to set pipeline
variables, run a manual pipeline and set
LAB_WINNER=manual-pipeline. That pipeline variable
should outrank the project value. Record
CI_PIPELINE_SOURCE, the pipeline ID, ref/SHA, and
effective value.
If the project has pipeline variables disabled or restricted, do not weaken a real security baseline. Current GitLab recommends pipeline inputs for newer parameterized designs. For this chapter, document the expected precedence row and continue; the project-variable experiment already proves external settings can outrank YAML.
10. Challenge: pick the correct causal layer
You edit LAB_WINNER in the job from
yaml-job to yaml-job-2, rerun, and the log
still says project-setting. Which layer should you
inspect first?
Answer: variable provenance/precedence, specifically the project variable. Runner, image, shell, and job script are already working. Changing them would add noise without addressing the source that wins.
11. Cleanup only the exact Chapter 06 resources
-
Delete only
LAB_WINNER,LAB_MASKED_DEMO,LAB_CONFIG_FILE, andLAB_PROTECTED_FLAGcreated in the disposable project. -
Remove the exact
ch06/protectedprotection rule if you created it solely for the lab, then delete the disposable lab branches after verifying their names and SHAs. - Do not bulk-delete project variables or change group/instance settings.
- Preserve pipeline/job IDs and non-secret evidence files long enough to complete the checkpoint.
Knowledge check
Why should the lab print an allowlist instead of running env or printenv?
A full environment dump can expose unrelated credentials or sensitive metadata. The lesson needs only controlled synthetic values and execution identifiers.
After adding a project LAB_WINNER, why does changing the job-level YAML value not change the output?
The project variable has higher precedence than YAML variables.
What should LAB_CONFIG_FILE contain inside the job environment?
A temporary file path. The configured value is stored in that file.
Why test only presence for LAB_PROTECTED_FLAG?
The experiment concerns authorization/availability, not the variable content. Printing the value teaches an unsafe diagnostic habit.
If pipeline variables are disabled in the project, should you weaken the setting just to finish the course?
No. Use the documented simulation/expected precedence and keep the security baseline intact unless a disposable authorized lab explicitly requires a reversible change.
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.