Variables
Un runner de GitLab recibe diversas variables de entorno que describen el contexto de ejecución del pipeline — como la rama actual, tipo de evento (push, merge request, etc.), usuario responsable, entre otros. Estas variables permiten crear flujos inteligentes y personalizados, siendo especialmente útiles en la definición de rules, condicionales de ejecución y comportamientos específicos para cada escenario del CI/CD.
Vamos ya de entrada a ver qué tenemos de variables de entorno en un runner con lo que tenemos hasta ahora.
En cualquier job coloca un - env. El comando env imprime todas las variables de entorno directamente en el shell. Vamos a seguir en la consola.
.check: # Plantilla para stage check
stage: check
before_script:
- env # Cualquier job que herede esta plantilla imprimirá esto.
- npm ci
artifacts:
when: always
expire_in: "3 months"
Abre un MR solo para ejecutar el pipeline vamos a revisar el log. Algunas variables están enmascaradas porque fueron creadas para ser secretas si seguiste el flujo del estudio.
# Log de uno de los 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
Claro que ahí arriba hay variables que son de GitLab y otras que no lo son. Si observas tenemos variables del runner, de node, y del propio alpine.
Aquí está la documentación de las variables predefinidas de CI/CD.
Variables x Contexto
pre-pipeline: Antes de que inicie cualquier job. Solo GitLab "lo sabe". ej: CI_PIPELINE_SOURCE, CI_COMMIT_BRANCH.- Variables que GitLab evalúa antes de ejecutar el .gitlab-ci.yml.
- Útiles en rules, only, except (Veremos más adelante)
pipeline: Durante toda la ejecución del pipeline. ej: CI_PIPELINE_ID, CI_PROJECT_NAME- Viven durante todo el pipeline. Están disponibles para todos los jobs y plantillas.
job-only: Solo dentro de un job. Fuera de él, no existe. ej: CI_JOB_NAME, CI_JOB_ID, CI_RUNNER_ID- Solo existen cuando el job ya está ejecutándose. Si intentas acceder en una rule o condicional de pipeline, fallará. Ejemplo: CI_RUNNER_DESCRIPTION.
El runner no recibe todas las variables de todos los contextos.
pre-pipeline: Estas variables se resuelven antes de que el pipeline inicie. Útiles en rules, workflow, etc, perono van al runner.pipeline y job-only: Disponibles en la ejecución de los jobs y visibles para el runner.
Aunque estas sean bien conocidas, voy a marcar aquellas que necesitan estar en la cabeza.
| Variable | Contexto | Descripción |
|---|---|---|
CI_PIPELINE_ID | Job-only | ID único del pipeline en la instancia de GitLab. |
CI_PIPELINE_IID | Pipeline | Único solo dentro del proyecto |
| CI_COMMIT_SHA | Pre-pipeline | SHA completo del commit que disparó el pipeline. |
CI_COMMIT_REF_NAME | Pre-pipeline | Nombre de la rama o tag del commit. |
| CI_COMMIT_BRANCH | Pre-pipeline | Nombre de la rama, si es un pipeline de rama. |
| CI_COMMIT_TAG | Pre-pipeline | Nombre del tag, si es un pipeline de tag. |
CI_PROJECT_PATH | Pre-pipeline | Ruta completa del proyecto (namespace/proyecto). |
CI_PROJECT_DIR | Job-only | Directorio donde el código fue clonado en el runner. No siempre los runners clonan los proyectos en el mismo lugar. |
| CI_JOB_NAME | Pipeline | Nombre del job actual. Generalmente usado para log y notificaciones |
| CI_JOB_STAGE | Pipeline | Etapa del job en el pipeline (build, test, deploy, etc.). Generalmente usado para log y notificaciones |
| CI_JOB_STATUS | Job-only | Estado actual del job (éxito, fallo, cancelado). Generalmente usado para log y notificaciones |
CI_JOB_TOKEN | Job-only | Token usado para autenticación en APIs y artefactos. Este token tiene limitaciones. |
CI_PIPELINE_SOURCE | Pre-pipeline | Origen del pipeline (push, merge_request_event, trigger, etc). Esta es una de las más importantes |
CI_COMMIT_MESSAGE | Pre-pipeline | Mensaje completo del commit. Es bueno para validar si el mensaje del commit está en los estándares de commitizen por ejemplo |
| CI_REGISTRY_IMAGE | Pre-pipeline | URL de la imagen Docker en el registry del proyecto. Útil si se va a subir una imagen al propio image registry de GitLab. |
CI_ENVIRONMENT_NAME | Pipeline | Nombre del entorno (staging, prod, etc.), si está configurado en el job. |
CI_ENVIRONMENT_URL | Pipeline | URL del entorno, si está definida. |
CI_COMMIT_AUTHOR | Pre-pipeline | Nombre y email del autor del commit. Bueno para auditoría y disparar notificación. |
CI_PIPELINE_URL | Job-only | URL para detalles del pipeline en GitLab. |
GITLAB_USER_LOGIN | Pipeline | Nombre de usuario de quien disparó el pipeline (si aplica). No siempre es igual a quien hizo el commit. Generalmente quien dispara el pipeline suele hacer el commit si es automático, pero no siempre. |
CI_COMMIT_TIMESTAMP | Pre-pipeline | es una variable pre-pipeline que trae la fecha y hora exacta del commit, en formato ISO 8601 (ej: 2025-04-30T12:45:32+00:00. Bueno para incluir en nombres, changelogs, auditoría. |
TRIGGER_PAYLOAD | Pipeline | contiene el contenido bruto del webhook que disparó el pipeline — en JSON. Solo aparece cuando el pipeline se inicia vía disparador externo (como un webhook de GitHub, Jira, otro GitLab, etc). Muy utilizada, inclusive cuando utilizamos gitlab-ci como backend! |
Cuando ocurre un merge request tenemos algunas variables específicas que también son importantes.
Algunas variables aparecerán como _SLUG. es prácticamente lo mismo, pero facilita bastante.
- Sustituye todo lo que no sea
a-zo0-9por guión(-) - Limita a 63 caracteres (límite común en DNS/K8s)
- Elimina guiones al inicio/fin, si los hubiera
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
Cuando necesitas el nombre original de la rama, con mayúsculas y símbolos intactos (ej: validación de patrón, git checkout, logs más legibles) no utilices slug. Es bueno definir un estándar en el equipo.
Usa slug cuando vayas a crear nombres de entornos, namespaces en Kubernetes, URLs (ej: review apps), nombre de ramas en tags Docker (app:${CI_COMMIT_REF_SLUG}), Labels, directorios, archivos generados automáticamente, etc.
| Variable | Contexto | Descripción / Uso Práctico |
|---|---|---|
CI_MERGE_REQUEST_TITLE | pipeline | Verifica o muestra el título del MR. Útil para validar patrones de nomenclatura. |
CI_MERGE_REQUEST_ID | pipeline | ID global del MR. Puede usarse en logs o llamadas en la API. |
| CI_MERGE_REQUEST_IID | pipeline | ID del MR en el proyecto (ese número de la URL). Útil en scripts o dashboards. |
| CI_MERGE_REQUEST_ASSIGNEES | pipeline | Lista de responsables. Se puede validar si el MR está asignado a alguien. |
CI_MERGE_REQUEST_APPROVED | pipeline | Verificar si ya fue aprobado antes de continuar (ej: traba release sin aprobación). |
CI_MERGE_REQUEST_LABELS | pipeline | Muy útil para lógica condicional basada en labels (deploy, hotfix...). |
| CI_MERGE_REQUEST_DESCRIPTION | pipeline | Puede usarse para análisis de contenido del MR (ej: changelog automatizado). |
| CI_MERGE_REQUEST_DRAFT | pipeline | Evita que MRs en borrador pasen al entorno o sean desplegados. |
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME | pre-pipeline | Rama de origen del MR (quien quiere hacer merge). |
CI_MERGE_REQUEST_TARGET_BRANCH_NAME | pre-pipeline | Rama de destino del MR (ej: main, develop). Esta se puede fijar en la cabeza |
| CI_MERGE_REQUEST_EVENT_TYPE | pre-pipeline | Útil para entender el tipo de pipeline del MR (merge_train, detached, etc). |
| CI_MERGE_REQUEST_SQUASH_ON_MERGE | pipeline | Para aplicar lógica si squash está activado (ej: validar commit único). |
Existen algunas variables interesantes que es bueno conocer.
Las variables CHAT_CHANNEL, CHAT_INPUT, CHAT_USER_ID son específicas de ChatOps en GitLab, es decir, cuando un pipeline se dispara a partir de un comando enviado por chat (como Slack) usando integración con GitLab ChatOps.
CI_PIPELINE_SOURCE
Esta variable es muy importante. Lo que disparó el pipeline puede cambiar todo el flujo. Los posibles valores son:
| Valor | Origen del pipeline |
|---|---|
| push | Push en una rama |
| web | Creado manualmente vía UI |
| trigger | Disparado por un trigger token |
| schedule | Pipeline agendado |
| api | Pipeline creado vía API REST |
| external | Disparado por integración externa (como webhook de otro sistema) |
| pipeline | Pipeline creado por otro pipeline (multi-project o child pipeline) |
| chat | Pipeline iniciado por comando vía ChatOps |
| webide | Iniciado a partir del Web IDE |
| merge_request_event | Pipeline de merge request (tipo antiguo) |
| external_pull_request_event | Pipeline disparado por pull request externo (ej: GitHub) |
| parent_pipeline | Pipeline padre que generó este |
| ondemand_dast_scan | Pipeline iniciado por un escaneo DAST bajo demanda |
| security_orchestration_policy | Disparado por una política de seguridad |
Sé que son muchas variables, pero la idea es crear algo que podamos consultar más rápido. La documentación oficial siempre está disponible y conocer un poco de lo que podemos hacer nos da alas. ¡Ahora con los poderes correctos podemos comenzar a crear el flujo de las cosas!
Job Variables
Si la misma variable (misma key) se define en el repositorio y a nivel de job, esta sobrescribirá el valor localmente dentro del job.
Imaginemos que tenemos MY_VAR definida en el repositorio como MY_VAR=hello
build:
stage: build
variables:
MY_VAR: my-var # Variable a nivel de job
script:
- echo $MY_VAR # resultará my-var