Variables, Secrets, and Protected Configuration
This domain covers where variable values come from, which one wins when names collide, and how secrets are restricted to trusted refs.
Precedence
Highest to lowest: trigger, manual, and scheduled pipeline variables → project variables → group variables → instance variables → job-level variables: → global variables: → deployment variables → predefined variables. Because predefined variables come last, a job-level variables: CI_COMMIT_BRANCH: main overrides the real branch - a classic self-inflicted bug.
Values are strings; quote booleans and nulls (VAR: "false"). $VAR expansion happens in variables: values and in script, but a variable cannot reference itself. Inside script, use $$ to emit a literal $ that GitLab would otherwise try to expand.
Protected, masked, and scoped
- Protected variables are injected only into pipelines on protected branches or tags, and only when the job runs on a protected runner. On a feature branch the variable is simply absent (empty string), which surfaces as an auth failure rather than a clear error.
- Masked variables keep their value out of job logs. Requirements: at least 8 characters, one line, no whitespace, and a base64/hex-compatible character set.
- File variables are written to a temporary file; the variable holds the path, which is what tools expecting a credential file need.
artifacts:reports:dotenvpasses variables from one job to downstream jobs in the same pipeline.
Worked example
Project variable API_TOKEN is protected and set to abc123. A merge request pipeline runs on a feature branch and executes curl -H "PRIVATE-TOKEN: $API_TOKEN". The request returns 401. Reasoning: the branch is not protected, so GitLab never injected the variable; $API_TOKEN expanded to an empty string and an empty header was sent. Fix it by protecting the branch, or by avoiding protected variables in MR pipelines - for example exchanging an OIDC id_tokens JWT for a short-lived secret.
Secrets
secrets: with vault: retrieves secrets at job runtime, authenticated by a JWT from id_tokens rather than a stored credential. CI_JOB_TOKEN is scoped to the job and pipeline and is the correct credential for the container and package registries.
Traps
- Assuming a protected variable is "empty by mistake"; check the protection status of both the branch and the runner.
- Trying to mask a short or multi-line secret.
- Expecting
export FOO=barinside one job's script to reach another job.