Skip to main content

Variables

A GitLab runner receives several environment variables that describe the pipeline execution context — such as current branch, event type (push, merge request, etc.), responsible user, among others. These variables allow creating intelligent and personalized flows, being especially useful in defining rules, execution conditionals, and specific behaviors for each CI/CD scenario.

Let's right away see what environment variables we have in a runner with what we have so far.

In any job put an - env. The env command prints all environment variables directly in the shell. Let's follow in the console.

.check: # Template for check stage
stage: check
before_script:
- env # Any job that inherits this template will print this.
- npm ci
artifacts:
when: always
expire_in: "3 months"

Open an MR just to execute the pipeline, let's check the log. Some variables are masked because they were created to be secret if you followed the study flow.

# Log from one of the jobs.
Skipping Git submodules setup
Executing "step_script" stage of the job script
Using docker image sha256:461edc13e56b039ebc3d898b858ac3acea00c47f31e93ec1258379cae8990522 for node:22-alpine with digest node@sha256:ad1aedbcc1b0575074a91ac146d6956476c1f9985994810e4ee02efd932a68fd ...
$ env
FF_USE_WINDOWS_LEGACY_PROCESS_STRATEGY=false
FF_SCRIPT_SECTIONS=false
FF_POSIXLY_CORRECT_ESCAPES=false
CI_SERVER_VERSION_PATCH=0
CI_SERVER_REVISION=eb738e3a3cd
CI_COMMIT_SHORT_SHA=0f053f0d
GITLAB_USER_LOGIN=davidpuziol
CI_DEPENDENCY_PROXY_PASSWORD=[MASKED]
CI_DEPENDENCY_PROXY_SERVER=gitlab.com:443
FF_USE_DUMB_INIT_WITH_KUBERNETES_EXECUTOR=false
CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED=true
FF_USE_LEGACY_KUBERNETES_EXECUTION_STRATEGY=false
FF_LOG_IMAGES_CONFIGURED_FOR_JOB=false
FF_USE_LEGACY_S3_CACHE_ADAPTER=false
CI=true
CI_SERVER_PROTOCOL=https
CI_PROJECT_NAME=learn-gitlab-app
CI_RUNNER_REVISION=0f67ff19
NODE_VERSION=22.15.0
HOSTNAME=runner-jyvyfkmfg-project-69186599-concurrent-1
CI_JOB_STAGE=check
CI_PROJECT_DESCRIPTION=
CI_COMMIT_DESCRIPTION=
YARN_VERSION=1.22.22
CI_DEPENDENCY_PROXY_USER=gitlab-ci-token
CI_SERVER_VERSION=18.0.0-pre
CI_MERGE_REQUEST_SOURCE_PROJECT_ID=69186599
SHLVL=3
FF_DISABLE_POWERSHELL_STDIN=false
FF_CLEAN_UP_FAILED_CACHE_EXTRACT=false
FF_DISABLE_AUTOMATIC_TOKEN_ROTATION=false
CI_PROJECT_ROOT_NAMESPACE=puziol
HOME=/root
OLDPWD=/
FF_NETWORK_PER_BUILD=false
CI_JOB_ID=9882688551
CI_SERVER_HOST=gitlab.com
CI_COMMIT_REF_NAME=pipe/variables
FF_RESOLVE_FULL_TLS_CHAIN=false
CI_PIPELINE_SOURCE=merge_request_event
CI_MERGE_REQUEST_TARGET_BRANCH_SHA=
CI_RUNNER_VERSION=17.11.0
FF_SKIP_NOOP_BUILD_STAGES=true
FF_USE_FASTZIP=false
FF_USE_WINDOWS_JOB_OBJECT=false
CI_BUILDS_DIR=/builds
CI_SERVER_VERSION_MAJOR=18
CI_DEFAULT_BRANCH=main
CI_MERGE_REQUEST_SOURCE_PROJECT_PATH=puziol/learn-gitlab-app
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME=pipe/variables
CI_REGISTRY_PASSWORD=[MASKED]
CI_SERVER_URL=https://gitlab.com
CI_TEMPLATE_REGISTRY_HOST=registry.gitlab.com
CI_COMMIT_REF_PROTECTED=false
CI_MERGE_REQUEST_DIFF_ID=1343611661
GITLAB_FEATURES=ldap_group_sync,multiple_ldap_servers,seat_link,seat_usage_quotas,pipelines_usage_quotas,transfer_usage_quotas,product_analytics_usage_quotas,zoekt_code_search,repository_size_limit,elastic_search,admin_audit_log,auditor_user,custom_file_templates,custom_project_templates,db_load_balancing,default_branch_protection_restriction_in_groups,extended_audit_events,external_authorization_service_api_management,geo,instance_level_scim,ldap_group_sync_filter,object_storage,pages_size_limit,project_aliases,disable_private_profiles,password_complexity,amazon_q,enterprise_templates,git_abuse_rate_limit,integrations_allow_list,required_ci_templates,runner_maintenance_note,runner_performance_insights,runner_upgrade_management,observability_alerts
CI_PROJECT_ID=69186599
CI_REGISTRY_IMAGE=registry.gitlab.com/puziol/learn-gitlab-app
FF_USE_LEGACY_GCS_CACHE_ADAPTER=false
GITLAB_CI=true
CI_COMMIT_SHA=0f053f0db8b19830a962bde2e22f68ea73239f03
CI_MERGE_REQUEST_SOURCE_BRANCH_PROTECTED=false
FF_RETRIEVE_POD_WARNING_EVENTS=true
CI_CONCURRENT_ID=1
FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR=false
FF_USE_DOCKER_AUTOSCALER_DIAL_STDIO=true
CI_REGISTRY_USER=gitlab-ci-token
CI_SERVER_PORT=443
CI_MERGE_REQUEST_SQUASH_ON_MERGE=true
CI_PROJECT_DIR=/builds/puziol/learn-gitlab-app
CI_PROJECT_PATH=puziol/learn-gitlab-app
CI_MERGE_REQUEST_PROJECT_URL=https://gitlab.com/puziol/learn-gitlab-app
FF_ENABLE_JOB_CLEANUP=false
FF_WAIT_FOR_POD_TO_BE_REACHABLE=false
CI_PROJECT_NAMESPACE=puziol
CI_COMMIT_TIMESTAMP=2025-04-30T11:27:02-03:00
FF_USE_DIRECT_DOWNLOAD=true
FF_USE_DYNAMIC_TRACE_FORCE_SEND_INTERVAL=false
CI_JOB_TOKEN=[MASKED]
CI_NODE_TOTAL=1
CI_SERVER_NAME=GitLab
CI_MERGE_REQUEST_DESCRIPTION=
CI_PROJECT_NAMESPACE_ID=106089966
CI_PIPELINE_CREATED_AT=2025-04-30T14:27:45Z
CI_MERGE_REQUEST_SOURCE_BRANCH_SHA=
CI_MERGE_REQUEST_DRAFT=false
CI_CONCURRENT_PROJECT_ID=1
CI_JOB_NAME_SLUG=lint-test
CI_MERGE_REQUEST_IID=6
RUNNER_TEMP_PROJECT_DIR=/builds/puziol/learn-gitlab-app.tmp
FF_KUBERNETES_HONOR_ENTRYPOINT=false
CI_PIPELINE_URL=https://gitlab.com/puziol/learn-gitlab-app/-/pipelines/1794394989
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
FF_EXPORT_HIGH_CARDINALITY_METRICS=false
CI_JOB_STARTED_AT=2025-04-30T14:27:50Z
CI_SERVER_VERSION_MINOR=0
CI_RUNNER_DESCRIPTION=general-debian
FF_USE_NEW_BASH_EVAL_STRATEGY=false
FF_DISABLE_UMASK_FOR_KUBERNETES_EXECUTOR=false
FF_MASK_ALL_DEFAULT_TOKENS=true
CI_PROJECT_TITLE=learn-gitlab-app
CI_PROJECT_VISIBILITY=private
CI_COMMIT_TITLE=prints env
GITLAB_USER_EMAIL=[email protected]
FF_USE_GIT_BUNDLE_URIS=true
FF_USE_NATIVE_STEPS=true
FF_USE_FLEETING_ACQUIRE_HEARTBEATS=false
CI_SERVER=yes
CI_JOB_GROUP_NAME=lint-test
CI_MERGE_REQUEST_EVENT_TYPE=detached
FF_TIMESTAMPS=false
CI_PROJECT_REPOSITORY_LANGUAGES=javascript,css,html
CI_MERGE_REQUEST_ID=380143752
CI_PAGES_URL=https://learn-gitlab-app-414996.gitlab.io
FF_SET_PERMISSIONS_BEFORE_CLEANUP=true
FF_PRINT_POD_EVENTS=false
CI_SERVER_FQDN=gitlab.com
CI_COMMIT_AUTHOR=David Puziol <[email protected]>
CI_JOB_IMAGE=node:22-alpine
CI_RUNNER_SHORT_TOKEN=jyvyfkmfg
CI_PAGES_DOMAIN=gitlab.io
CI_JOB_TIMEOUT=3600
CI_REPOSITORY_URL=https://gitlab-ci-token:[MASKED]@gitlab.com/puziol/learn-gitlab-app.git
CI_PROJECT_CLASSIFICATION_LABEL=
CI_PIPELINE_NAME=
GITLAB_ENV=/builds/puziol/learn-gitlab-app.tmp/gitlab_runner_env
FF_USE_POWERSHELL_PATH_RESOLVER=false
FF_GIT_URLS_WITHOUT_TOKENS=false
CI_API_GRAPHQL_URL=https://gitlab.com/api/graphql
CI_RUNNER_ID=47096042
CI_REGISTRY=registry.gitlab.com
CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX=gitlab.com:443/puziol/dependency_proxy/containers
CI_API_V4_URL=https://gitlab.com/api/v4
CI_MERGE_REQUEST_SOURCE_PROJECT_URL=https://gitlab.com/puziol/learn-gitlab-app
GITLAB_USER_NAME=David Puziol Prata
CI_PIPELINE_IID=33
CI_MERGE_REQUEST_PROJECT_ID=69186599
FF_USE_POD_ACTIVE_DEADLINE_SECONDS=true
FF_USE_ADVANCED_POD_SPEC_CONFIGURATION=false
CI_JOB_URL=https://gitlab.com/puziol/learn-gitlab-app/-/jobs/9882688551
CI_SERVER_SHELL_SSH_HOST=gitlab.com
CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED=false
CI_RUNNER_EXECUTABLE_ARCH=linux/amd64
FF_TEST_FEATURE=false
FF_USE_INIT_WITH_DOCKER_EXECUTOR=false
CI_MERGE_REQUEST_REF_PATH=refs/merge-requests/6/head
CI_COMMIT_REF_SLUG=pipe-variables
CI_DISPOSABLE_ENVIRONMENT=true
PWD=/builds/puziol/learn-gitlab-app
FF_SECRET_RESOLVING_FAILS_IF_MISSING=true
CI_MERGE_REQUEST_PROJECT_PATH=puziol/learn-gitlab-app
CI_RUNNER_TAGS=["general", "debian"]
CI_SERVER_TLS_CA_FILE=/builds/puziol/learn-gitlab-app.tmp/CI_SERVER_TLS_CA_FILE
CI_PIPELINE_ID=1794394989
CI_PROJECT_PATH_SLUG=puziol-learn-gitlab-app
CI_PROJECT_URL=https://gitlab.com/puziol/learn-gitlab-app
CI_CONFIG_PATH=.gitlab-ci.yml
CI_COMMIT_BEFORE_SHA=0000000000000000000000000000000000000000
CI_MERGE_REQUEST_TITLE=Pipe/variables
CI_PROJECT_NAMESPACE_SLUG=puziol
CI_COMMIT_MESSAGE=prints env
FF_ENABLE_BASH_EXIT_CODE_CHECK=false
CI_PAGES_HOSTNAME=learn-gitlab-app-414996.gitlab.io
CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX=gitlab.com:443/puziol/dependency_proxy/containers
GITLAB_USER_ID=4300841
CI_JOB_STATUS=running
CI_JOB_NAME=lint-test
CI_SERVER_SHELL_SSH_PORT=22
CI_MERGE_REQUEST_DIFF_BASE_SHA=093afcb937eeffbc005998803ace71c2887f1c88
CI_MERGE_REQUEST_TARGET_BRANCH_NAME=main
CI_OPEN_MERGE_REQUESTS=puziol/learn-gitlab-app!6

Of course there are variables from GitLab and others that aren't. If you observe, we have variables from the runner, from node, and from alpine itself.

Here's the documentation for predefined CI/CD variables.

Variables x Context​

  • pre-pipeline: Before any job starts. Only GitLab "knows". ex: CI_PIPELINE_SOURCE, CI_COMMIT_BRANCH.
    • Variables that GitLab evaluates before running .gitlab-ci.yml.
    • Useful in rules, only, except (We'll see later)
  • pipeline: During the entire pipeline execution. ex: CI_PIPELINE_ID, CI_PROJECT_NAME
    • Live during the entire pipeline. Available to all jobs and templates.
  • job-only: Only inside a job. Outside it, doesn't exist. ex: CI_JOB_NAME, CI_JOB_ID, CI_RUNNER_ID
    • Only exist when the job is already running. If you try to access in a rule or pipeline conditional, it will fail. Example: CI_RUNNER_DESCRIPTION.

The runner doesn't receive all variables from all contexts.

  • pre-pipeline: These variables are resolved before the pipeline starts. Useful in rules, workflow, etc., but don't go to the runner.
  • pipeline and job-only: Available in job execution and visible to the runner.

Although these are well known, I'll mark those that need to be in your head.

VariableContextDescription
CI_PIPELINE_IDJob-onlyUnique pipeline ID in the GitLab instance.
CI_PIPELINE_IIDPipelineUnique only within the project
CI_COMMIT_SHAPre-pipelineFull SHA of the commit that triggered the pipeline.
CI_COMMIT_REF_NAMEPre-pipelineBranch or tag name of the commit.
CI_COMMIT_BRANCHPre-pipelineBranch name, if it's a branch pipeline.
CI_COMMIT_TAGPre-pipelineTag name, if it's a tag pipeline.
CI_PROJECT_PATHPre-pipelineFull project path (namespace/project).
CI_PROJECT_DIRJob-onlyDirectory where code was cloned in the runner. Runners don't always clone projects in the same place.
CI_JOB_NAMEPipelineCurrent job name. Usually used for logging and notifications
CI_JOB_STAGEPipelineJob stage in the pipeline (build, test, deploy, etc.). Usually used for logging and notifications
CI_JOB_STATUSJob-onlyCurrent job status (success, failure, canceled). Usually used for logging and notifications
CI_JOB_TOKENJob-onlyToken used for authentication in APIs and artifacts. This token has limitations.
CI_PIPELINE_SOURCEPre-pipelinePipeline origin (push, merge_request_event, trigger, etc). This is one of the most important
CI_COMMIT_MESSAGEPre-pipelineComplete commit message. Good for validating if commit message is in commitizen standards for example
CI_REGISTRY_IMAGEPre-pipelineDocker image URL in the project registry. Useful if uploading an image to GitLab's own image registry.
CI_ENVIRONMENT_NAMEPipelineEnvironment name (staging, prod, etc.), if configured in the job.
CI_ENVIRONMENT_URLPipelineEnvironment URL, if defined.
CI_COMMIT_AUTHORPre-pipelineCommit author name and email. Good for auditing and triggering notifications.
CI_PIPELINE_URLJob-onlyURL for pipeline details in GitLab.
GITLAB_USER_LOGINPipelineUsername of who triggered the pipeline (if applicable). Not always the same as who made the commit. Usually who triggers the pipeline tends to make the commit if automatic, but not always.
CI_COMMIT_TIMESTAMPPre-pipelineis a pre-pipeline variable that brings the exact date and time of the commit, in ISO 8601 format (ex: 2025-04-30T12:45:32+00:00. Good for including in names, changelogs, auditing.
TRIGGER_PAYLOADPipelinecontains the raw content of the webhook that triggered the pipeline — in JSON. Only appears when the pipeline is initiated via external trigger (like a webhook from GitHub, Jira, another GitLab, etc). Very used, including when we use gitlab-ci as backend!

When a merge request happens, we have some specific variables that are also important.

Some variables will appear as _SLUG. it's practically the same thing, but it helps a lot.

  • Replaces everything that's not a-z or 0-9 with hyphen (-)
  • Limits to 63 characters (common limit in DNS/K8s)
  • Removes hyphens at the beginning/end, if any
CI_COMMIT_REF_NAME=feature/Very_Cool-Feature_42
CI_COMMIT_REF_SLUG=feature-very-cool-feature-42

CI_COMMIT_REF_SLUG=pipe/variables
CI_COMMIT_REF_SLUG=pipe-variables

CI_JOB_NAME=lint-test
CI_JOB_NAME_SLUG=lint-test

CI_PROJECT_PATH=puziol/learn-gitlab-app
CI_PROJECT_PATH_SLUG=puziol/learn-gitlab-app

CI_PROJECT_NAMESPACE=puziol
CI_PROJECT_NAMESPACE_SLUG=puziol

When you need the original branch name, with case and symbols intact (ex: pattern validation, git checkout, more readable logs) don't use slug. It's good to define a pattern in the team.

Use slug when creating environment names, Kubernetes namespaces, URLs (ex: review apps), branch names in Docker tags (app:${CI_COMMIT_REF_SLUG}), labels, directories, automatically generated files, etc.

VariableContextDescription / Practical Use
CI_MERGE_REQUEST_TITLEpipelineVerifies or displays MR title. Useful for validating naming patterns.
CI_MERGE_REQUEST_IDpipelineMR global ID. Can be used in logs or API calls.
CI_MERGE_REQUEST_IIDpipelineMR ID in the project (that URL number). Useful in scripts or dashboards.
CI_MERGE_REQUEST_ASSIGNEESpipelineList of assignees. Can validate if MR is assigned to someone.
CI_MERGE_REQUEST_APPROVEDpipelineCheck if it's already approved before proceeding (ex: lock release without approval).
CI_MERGE_REQUEST_LABELSpipelineVery useful for conditional logic based on labels (deploy, hotfix...).
CI_MERGE_REQUEST_DESCRIPTIONpipelineCan be used for MR content analysis (ex: automated changelog).
CI_MERGE_REQUEST_DRAFTpipelinePrevents MRs in draft from going to environment or being deployed.
CI_MERGE_REQUEST_SOURCE_BRANCH_NAMEpre-pipelineMR source branch (who wants to merge).
CI_MERGE_REQUEST_TARGET_BRANCH_NAMEpre-pipelineMR target branch (ex: main, develop). This one you can memorize
CI_MERGE_REQUEST_EVENT_TYPEpre-pipelineUseful for understanding MR pipeline type (merge_train, detached, etc).
CI_MERGE_REQUEST_SQUASH_ON_MERGEpipelineTo apply logic if squash is activated (ex: validate single commit).

There are some interesting variables that are good to know.

The CHAT_CHANNEL, CHAT_INPUT, CHAT_USER_ID variables are specific to ChatOps in GitLab, that is, when a pipeline is triggered from a command sent via chat (like Slack) using GitLab ChatOps integration.

CI_PIPELINE_SOURCE​

This variable is very important. What triggered the pipeline can change the entire flow. The possible values are:

ValuePipeline Origin
pushPush to a branch
webCreated manually via UI
triggerTriggered by a trigger token
scheduleScheduled pipeline
apiPipeline created via REST API
externalTriggered by external integration (like webhook from another system)
pipelinePipeline created by another pipeline (multi-project or child pipeline)
chatPipeline initiated by command via ChatOps
webideInitiated from Web IDE
merge_request_eventMerge request pipeline (old type)
external_pull_request_eventPipeline triggered by external pull request (ex: GitHub)
parent_pipelineParent pipeline that generated this one
ondemand_dast_scanPipeline initiated by on-demand DAST scan
security_orchestration_policyTriggered by a security policy

I know there are many variables, but the idea is to create something we can consult faster. The official documentation is always available and knowing a bit of what we can do gives us wings. Now with the right powers we can start creating the flow of things!

Job Variables​

If the same variable (same key) is defined in the repository and at job level, it will overwrite the value locally within the job.

Let's imagine we have MY_VAR defined in the repository as MY_VAR=hello

build:
stage: build
variables:
MY_VAR: my-var # Job-level var
script:
- echo $MY_VAR # will result in my-var