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., butdon'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.
| Variable | Context | Description |
|---|---|---|
CI_PIPELINE_ID | Job-only | Unique pipeline ID in the GitLab instance. |
CI_PIPELINE_IID | Pipeline | Unique only within the project |
| CI_COMMIT_SHA | Pre-pipeline | Full SHA of the commit that triggered the pipeline. |
CI_COMMIT_REF_NAME | Pre-pipeline | Branch or tag name of the commit. |
| CI_COMMIT_BRANCH | Pre-pipeline | Branch name, if it's a branch pipeline. |
| CI_COMMIT_TAG | Pre-pipeline | Tag name, if it's a tag pipeline. |
CI_PROJECT_PATH | Pre-pipeline | Full project path (namespace/project). |
CI_PROJECT_DIR | Job-only | Directory where code was cloned in the runner. Runners don't always clone projects in the same place. |
| CI_JOB_NAME | Pipeline | Current job name. Usually used for logging and notifications |
| CI_JOB_STAGE | Pipeline | Job stage in the pipeline (build, test, deploy, etc.). Usually used for logging and notifications |
| CI_JOB_STATUS | Job-only | Current job status (success, failure, canceled). Usually used for logging and notifications |
CI_JOB_TOKEN | Job-only | Token used for authentication in APIs and artifacts. This token has limitations. |
CI_PIPELINE_SOURCE | Pre-pipeline | Pipeline origin (push, merge_request_event, trigger, etc). This is one of the most important |
CI_COMMIT_MESSAGE | Pre-pipeline | Complete commit message. Good for validating if commit message is in commitizen standards for example |
| CI_REGISTRY_IMAGE | Pre-pipeline | Docker image URL in the project registry. Useful if uploading an image to GitLab's own image registry. |
CI_ENVIRONMENT_NAME | Pipeline | Environment name (staging, prod, etc.), if configured in the job. |
CI_ENVIRONMENT_URL | Pipeline | Environment URL, if defined. |
CI_COMMIT_AUTHOR | Pre-pipeline | Commit author name and email. Good for auditing and triggering notifications. |
CI_PIPELINE_URL | Job-only | URL for pipeline details in GitLab. |
GITLAB_USER_LOGIN | Pipeline | Username 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_TIMESTAMP | Pre-pipeline | is 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_PAYLOAD | Pipeline | contains 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-zor0-9with 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.
| Variable | Context | Description / Practical Use |
|---|---|---|
CI_MERGE_REQUEST_TITLE | pipeline | Verifies or displays MR title. Useful for validating naming patterns. |
CI_MERGE_REQUEST_ID | pipeline | MR global ID. Can be used in logs or API calls. |
| CI_MERGE_REQUEST_IID | pipeline | MR ID in the project (that URL number). Useful in scripts or dashboards. |
| CI_MERGE_REQUEST_ASSIGNEES | pipeline | List of assignees. Can validate if MR is assigned to someone. |
CI_MERGE_REQUEST_APPROVED | pipeline | Check if it's already approved before proceeding (ex: lock release without approval). |
CI_MERGE_REQUEST_LABELS | pipeline | Very useful for conditional logic based on labels (deploy, hotfix...). |
| CI_MERGE_REQUEST_DESCRIPTION | pipeline | Can be used for MR content analysis (ex: automated changelog). |
| CI_MERGE_REQUEST_DRAFT | pipeline | Prevents MRs in draft from going to environment or being deployed. |
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME | pre-pipeline | MR source branch (who wants to merge). |
CI_MERGE_REQUEST_TARGET_BRANCH_NAME | pre-pipeline | MR target branch (ex: main, develop). This one you can memorize |
| CI_MERGE_REQUEST_EVENT_TYPE | pre-pipeline | Useful for understanding MR pipeline type (merge_train, detached, etc). |
| CI_MERGE_REQUEST_SQUASH_ON_MERGE | pipeline | To 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:
| Value | Pipeline Origin |
|---|---|
| push | Push to a branch |
| web | Created manually via UI |
| trigger | Triggered by a trigger token |
| schedule | Scheduled pipeline |
| api | Pipeline created via REST API |
| external | Triggered by external integration (like webhook from another system) |
| pipeline | Pipeline created by another pipeline (multi-project or child pipeline) |
| chat | Pipeline initiated by command via ChatOps |
| webide | Initiated from Web IDE |
| merge_request_event | Merge request pipeline (old type) |
| external_pull_request_event | Pipeline triggered by external pull request (ex: GitHub) |
| parent_pipeline | Parent pipeline that generated this one |
| ondemand_dast_scan | Pipeline initiated by on-demand DAST scan |
| security_orchestration_policy | Triggered 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