Chapter 06Lesson 02~170 minutes

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.

Hands-onProject variablesFile variablesProtected refsEvidence

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.
Disposable-only lab: use a throwaway project you control. Every custom value below is synthetic. Do not reuse employer/customer variables or real credentials. The required path works on GitLab Free; protected-ref steps require enough project role to manage the disposable project, otherwise use the documented simulation path.

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.

Evidence requirement: preserve the baseline job ID and the project-variable job ID. A screenshot of only the final green pipeline is insufficient; the comparison must prove which configuration layer changed.

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.

  1. Run on the unprotected ch06/variables branch. Expected: protected_present=no.
  2. In the disposable project only, create a dedicated branch such as ch06/protected and protect that exact branch using the narrowest permissions appropriate to the lab.
  3. 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.

Security note: pipeline variables can override lower-precedence configuration and may be user-supplied. Treat them as untrusted control input unless constrained. Do not pass secrets this way merely for convenience.

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, and LAB_PROTECTED_FLAG created in the disposable project.
  • Remove the exact ch06/protected protection 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.
Next lesson

Configuration, Design Choices, and Tradeoffs

Choose the right variable source/type/control for real designs, including when to use inputs, settings, file variables, protected values, and external secret systems.

Knowledge check

Why should the lab print an allowlist instead of running env or printenv?

After adding a project LAB_WINNER, why does changing the job-level YAML value not change the output?

What should LAB_CONFIG_FILE contain inside the job environment?

Why test only presence for LAB_PROTECTED_FLAG?

If pipeline variables are disabled in the project, should you weaken the setting just to finish the course?

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.
Version and compatibility note

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.

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