Variables & Secrets
Jobs receive variables from multiple sources, layered by precedence (lowest to highest):
| Priority | Source | How to set |
|---|---|---|
| 1 (lowest) | CI-derived | Automatic (git SHA, branch, etc.) |
| 2 | Global YAML | variables: at top of .gitlab-ci.yml |
| 3 | Instance variables | Fetched from GitLab API (admin token; --secrets all) |
| 4 | Group variables | Fetched from GitLab API (--secrets all) |
| 5 | Project variables | Fetched from GitLab API |
| 6 | --env flags | glci run --env KEY=VALUE |
| 7 | --env-file | glci run --env-file .env.local |
| 8 | Pipeline preset env | env of a --pipeline/context preset in .glciconfig.toml |
| 9 | .glci.env | Auto-loaded from project root (add to .gitignore) |
| 10 | Dotenv artifacts | artifacts: reports: dotenv: from dependency jobs |
| 11 (highest) | Job YAML | variables: inside a job definition |
This mirrors GitLab’s order: instance, group, and project CI/CD variables override the global variables: block, with the more specific scope (project) winning over the broader ones (group, instance).
Inspecting resolved variables#
When a value isn’t what you expect — or a rules: clause matches when it shouldn’t (or doesn’t when it should) — inspect the resolved variables instead of guessing.
glci variables — static inspection#
glci variables resolves the variables for every job without running the pipeline, attributing each value to the layer it came from and showing how each job’s rules: evaluated. Because nothing executes, jobs that are excluded by their rules: are still shown (marked excluded), which is the main way to debug why a rule did or did not match.
It focuses on the variables you actually control. Predefined CI_* variables are hidden by default (pass --all to include them), with one exception: any predefined variable referenced by a rule’s if: is always shown, so you can see the value the rule was evaluated against.
glci variables # all jobs, current context
glci variables deploy_prod # only the named job(s)
glci variables --stage test # only jobs in a stage
glci variables --context branch=main # simulate a different context
glci variables --all # include predefined CI_* variables
glci variables --json # machine-readable output
Example:
deploy_prod (stage: deploy) [included, when: on_success]
Rules:
#0 $CI_COMMIT_BRANCH (main) == $CI_DEFAULT_BRANCH (main) → true
Variables:
KEY VALUE SOURCE
CI_COMMIT_BRANCH main predefined
DEPLOY_ENV production rules
GREETING hello global
The Rules section shows each rule’s condition with every $VAR expanded to its value, and whether the condition evaluated true or false (a failing changes:/exists: rule appends a short reason). The Variables SOURCE column reports where the winning value came from: predefined, global, flag (--env), env-file, glci-env, preset, job, rules, or predefined:job (per-job CI_JOB_NAME/CI_JOB_STAGE).
A job excluded by its rules still prints, so you can see why:
only_dev (stage: test) [excluded, when: never]
Rules:
#0 $CI_COMMIT_BRANCH (main) == "dev" → false
Variables:
KEY VALUE SOURCE
CI_COMMIT_BRANCH main predefined
...
With --json, each job carries a rule_trace (with matched booleans and the refs map of referenced variable values) and the full variables array — suitable for programmatic use.
Long values (e.g. CI_COMMIT_DESCRIPTION) and multi-line values are truncated to 512 characters and shown on a single line so the table stays readable. Pass --expand to print full, raw values. (--json always carries the full value.)
glci variables resolves everything available locally (no Docker, no network). It does not fetch GitLab API group/project secrets or runtime values (dotenv, CI_JOB_ID); for those, use --show-variables below.
glci run --show-variables#
Adding --show-variables to glci run prints each job’s fully resolved variable set — including API secrets, dotenv imports, and runtime CI_* values — together with its rule trace, to the terminal just before the job’s log:
glci run --show-variables # secrets masked, predefined CI_* hidden
glci run --show-variables --unmask # reveal masked/secret values
glci run --show-variables --all-variables # include predefined CI_* variables
glci run --show-variables --expand # do not truncate long values
deploy ── variables ──
Rules:
#0 $CI_COMMIT_BRANCH (main) == $CI_DEFAULT_BRANCH (main) → true
Variables:
KEY VALUE SOURCE
CI_COMMIT_BRANCH main predefined
CI_REGISTRY_PASSWORD [masked] registry
DEPLOY_ENV production rules
Like glci variables, predefined CI_* variables are hidden by default (use --all-variables), except those a rule references. Secret values (API group/project variables and any variable marked masked) are shown as [masked] by default; pass --unmask to print them in clear text.
Remote secrets#
Project, group, and instance CI/CD variables are fetched automatically when a GitLab token is available. Results are cached in daemon memory for 6 hours (never written to disk). The cache is lost on daemon restart.
glci run --secrets all # project + group + instance variables (default)
glci run --secrets project # project variables only
glci run --secrets none # skip remote fetching entirely
glci run --refresh-secrets # force a fresh fetch, bypassing the cache
Instance-level variables (GET /admin/ci/variables) require an administrator token. For non-admin tokens GitLab returns 403, and glci skips them with a warning in the daemon log — project and group variables are unaffected. This matches how real runners receive instance variables injected server-side; locally, glci can only read them when your token is allowed to. If your includes or jobs depend on an instance variable you can’t fetch (for example a non-admin token on a shared instance), supply it locally instead via .glci.env, --env, or a pipeline preset.
Configure the cache TTL in .glciconfig.toml:
[gitlab]
secrets_ttl = "6h" # default; set to "0" to disable caching
Local overrides with .glci.env#
Auto-loaded KEY=VALUE file from project root for local secrets. Add it to .gitignore. Masked and file-type variable attributes from the GitLab API are preserved and passed to the runner.
Variables in include:#
GitLab expands $VAR / ${VAR} in include: project:, ref:, and file: before fetching the included file, so a pipeline can point at a template repo through variables:
include:
- project: $_GITLAB_TEMPLATES_REPO
ref: $_GITLAB_TEMPLATES_REF
file: '/includes/main.yml'
glci resolves these the same way. The variables available during include expansion are, lowest to highest precedence: predefined CI_* variables, the global variables: block, then your locally-supplied variables (--env, --env-file, pipeline preset env, .glci.env). The local layers stand in for the instance/group/project CI/CD variables GitLab would normally supply.
On many self-managed instances (Drupal contrib’s gitlab_templates, for example) these are instance-level variables. glci fetches instance variables only with an admin token (see Remote secrets), and remote variables are fetched in parallel with parsing — so they are not available at include-expansion time. Supply any variable an include path depends on locally:
# .glci.env (gitignored)
_GITLAB_TEMPLATES_REPO=project/gitlab_templates
_GITLAB_TEMPLATES_REF=1.0.x
Inspect the resolved include paths with glci jobs or glci variables; a literal $VAR reaching the API (an HTTP 404 fetching projects/$VAR/...) means the variable wasn’t supplied at any of the layers above.
The same rule applies to child and cross-project pipelines (trigger: with include: or project:). They inherit the parent’s local layers (--env, --env-file, pipeline preset env, .glci.env), so any variable you supply locally is also available when the child’s own include: paths expand. Remote-fetched instance/group/project variables are excluded from include expansion in child pipelines too — if a child include path depends on one, supply it locally as above.
Token forwarding#
By default, the host’s real GitLab token is forwarded into jobs as CI_JOB_TOKEN. Disable with --no-token, or restrict which jobs receive it in .glciconfig.toml:
[token]
forward_host_token = true
stages = ["deploy"] # only deploy stage gets real token
jobs = ["release"] # specific job names
job_patterns = [".*-publish$"] # regex patterns
Jobs not matching the filter receive a synthetic mock-server token instead.
GitLab API token#
Required for include: project:, include: component:, fetching remote CI/CD variables, and auto-cloning trigger: project: targets.
Token resolution order (first match wins; --token flag always overrides):
GITLAB_TOKENorGITLAB_PRIVATE_TOKENenvironment variable[gitlab] tokenin.glciconfig.toml/~/.glci/config.tomlglab auth credential-helper– supports keyring, OAuth2 refresh, and PAT/CI tokens- Direct read of
~/.config/glab-cli/config.yml– legacy fallback
The simplest setup is glab auth login – the token is picked up automatically. The project path is auto-detected from your git remote; override with --project.
Fallback project path#
When no git remote is configured (common for local-only test repos), CI_PROJECT_PATH defaults to local/<dirname> so variables like $CI_PROJECT_PATH are always available in job scripts.
Unsupported variables#
About 20 CI/CD variables cannot be set locally because they require a real GitLab server: CI_DEPLOY_*, CI_UPSTREAM_*, CI_EXTERNAL_PULL_REQUEST_*, KUBECONFIG, CHAT_*. GITLAB_USER_* variables are approximated from git config.
GLCI_PREFER_API#
Set GLCI_PREFER_API=1 to use the GitLab Lint API as the primary parser instead of the offline parser. Useful for edge cases where the offline parser differs from GitLab’s server-side behavior.
Context-derived variables#
glci sets CI variables based on the --context flag. See Context Simulation for the full variable table per context type.