Переменные на уровне джобы и предопределённые переменные
Разбираем, как объявлять переменные в пайплайне и какие переменные GitLab даёт бесплатно.
Предопределённые переменные — переменные окружения, которые GitLab автоматически подставляет в каждую джобу: имя ветки, SHA коммита, URL реестра и десятки других.
Переменные — это клей, который соединяет статический .gitlab-ci.yml с динамическим контекстом конкретного запуска. Один и тот же файл должен вести себя по-разному в зависимости от того, на какой ветке его запустили, какой это коммит, идёт ли деплой в staging или в production. Без переменных пришлось бы хардкодить значения или плодить копии конфигов под каждое окружение. Поэтому грамотная работа с переменными — это в первую очередь работа с областями видимости и приоритетами: понимать, откуда значение пришло и что его перекроет.
Объявление своих переменных
Переменные задаются ключом variables — глобально или внутри джобы. Они доступны в командах как обычные переменные окружения оболочки.
variables:
APP_ENV: "staging"
build:
variables:
OPTIMIZE: "true"
script:
- echo "Окружение $APP_ENV, оптимизация $OPTIMIZE"Глобальные переменные видны всем джобам; объявленные внутри джобы — только ей. Если имя совпадает, локальное значение перекрывает глобальное.
Это даёт удобный паттерн «разумные умолчания плюс точечное переопределение»: глобально задаёте безопасное значение (например, APP_ENV: "staging"), а в конкретной джобе деплоя на прод переопределяете его на production. Важная тонкость о типах: значения переменных в GitLab — всегда строки. Поэтому OPTIMIZE: "true" — это строка true, а не булево значение, и в условиях rules вы будете сравнивать именно строки: $OPTIMIZE == "true". Кавычки вокруг значений стоит ставить осознанно, особенно если внутри есть пробелы, двоеточия или ведущие нули, которые YAML иначе попытается интерпретировать по-своему.
Предопределённые переменные
GitLab щедро снабжает джобы контекстом. Самые полезные:
| Переменная | Что содержит |
CI_COMMIT_REF_NAME | имя ветки или тега |
CI_COMMIT_SHA | полный SHA коммита |
CI_COMMIT_SHORT_SHA | короткий SHA (8 символов) |
CI_PIPELINE_SOURCE | источник пайплайна (push/schedule/...) |
CI_REGISTRY_IMAGE | базовый путь образа в реестре проекта |
CI_JOB_TOKEN | временный токен прав текущей джобы |
Их часто используют для тегирования образов уникальным SHA, чтобы каждый билд был отслеживаемым:
docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .Стоит сгруппировать предопределённые переменные по смыслу, чтобы держать их в голове. Есть коммитные (CI_COMMIT_SHA, CI_COMMIT_REF_NAME, CI_COMMIT_TAG, CI_COMMIT_MESSAGE) — они отвечают на вопрос «что именно мы собираем». Есть пайплайновые (CI_PIPELINE_SOURCE, CI_PIPELINE_ID) — «откуда и почему запущено»: по CI_PIPELINE_SOURCE вы отличаете обычный push от запуска по расписанию или merge-request-пайплайна. Есть джобные (CI_JOB_TOKEN, CI_JOB_ID) — контекст текущей задачи. И есть реестровые (CI_REGISTRY, CI_REGISTRY_IMAGE, CI_REGISTRY_USER) — для входа и публикации в Container Registry проекта. Отдельно отметим CI_JOB_TOKEN: это короткоживущий токен с правами текущей джобы, которым удобно логиниться в реестр или дёргать API GitLab без хранения отдельных секретов — он создаётся при старте джобы и протухает при её завершении.
Переменные в значениях
Переменные можно подставлять в другие значения YAML, в том числе в одну в другую. Это удобно для сборки путей и URL. Например, тег образа собирают из имени реестра и короткого SHA. Помните: подстановка происходит во время выполнения в оболочке, а не на этапе парсинга YAML.
Эта деталь — источник частой путаницы, поэтому остановимся подробнее. Когда вы пишете TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA", GitLab НЕ вычисляет значение в момент чтения файла. Он сохраняет строку как есть и разворачивает $CI_* уже в оболочке, перед запуском script. Практическое следствие: ссылаться можно только на переменные, которые к этому моменту существуют, и нельзя полагаться на сложную логику внутри YAML — вся вычислительная работа происходит в shell. Ещё одно следствие — знак доллара в литеральных строках надо экранировать (в GitLab это делается удвоением: $$), иначе раннер попытается подставить несуществующую переменную и получит пустую строку.
Сравнение с GitHub Actions
В GitHub Actions контекст доступен через выражения ${{ github.sha }} и контексты env, secrets. В GitLab всё проще и ближе к обычной оболочке: всё — переменные окружения вида $CI_*, без особого синтаксиса выражений. Это снижает порог входа, но и контекстных функций меньше.
Разница глубже, чем кажется. В Actions выражения ${{ ... }} вычисляются движком до запуска шага: там можно вызывать функции (contains, startsWith), обращаться к структурированным объектам (github.event.pull_request.title) — это маленький язык выражений. В GitLab такого слоя почти нет: контекст плоский и строковый, всё разруливается обычными средствами оболочки (if, case, grep). Это сознательный компромисс: GitLab проще объяснить и не нужно держать в голове, что вычисляется движком, а что — шеллом. Зато сложную условную логику в GitLab чаще выносят в отдельный rules-блок или в bash-скрипт, а не в выражения внутри значений.
Как работает под капотом
Перед запуском script раннер формирует окружение джобы: складывает вместе предопределённые переменные, переменные из .gitlab-ci.yml, переменные проекта/группы из настроек и экспортирует их в оболочку. Поэтому в командах они доступны как любые $VAR. Приоритет (от низшего к высшему) примерно такой: предопределённые → проектные → объявленные в файле → переданные вручную при запуске.
Этот порядок приоритета лучше всего держать как картинку слоёв: чем «ближе к ручному запуску», тем выше приоритет. Снизу — то, что GitLab знает сам (предопределённые), сверху — то, что человек указал явно прямо сейчас.
выше приоритет ^ значение из формы "Run pipeline" (ручной запуск) | trigger / downstream-переменные | variables: в .gitlab-ci.yml (джоба перекрывает глобальное) | переменные группы / проекта (Settings -> CI/CD) v предопределённые CI_* переменные ниже приоритет
Эта лесенка объясняет два повседневных сюрприза. Первый: значение, заданное в UI при ручном запуске, перекрывает то же имя из файла — и это правильно, ведь человек специально его указал. Второй: переменная проекта (например, секрет API) перекрывается одноимённой переменной из .gitlab-ci.yml — поэтому секреты НЕ дублируют в файле, чтобы случайно не затереть защищённое значение незащищённым.
Частые ошибки
- Считать, что
variablesвнутри джобы видны другим джобам — нет, только глобальные. - Путать порядок приоритета и удивляться, что значение из UI перекрыло файл (это ожидаемо для ручного запуска).
- Хранить секреты прямо в
variablesфайла — секретам место в защищённых CI/CD-переменных проекта (следующий раздел). - Забыть, что все значения — строки, и сравнивать
$FLAG == trueвместо$FLAG == "true". - Не экранировать литеральный
$в значениях и получить пустую строку вместо знака доллара.
Итоги
variablesобъявляют переменные глобально или на уровне джобы; локальные перекрывают глобальные.- GitLab даёт десятки предопределённых
CI_*переменных с контекстом коммита, пайплайна, джобы и реестра. - В командах всё это — обычные строковые переменные окружения оболочки; подстановка происходит в shell, а не в YAML.
- Приоритет растёт от предопределённых к ручному запуску; секреты держите в защищённых переменных проекта, а не в файле.