Learn (Almost) Anything
КаталогСкачать курс

Автоматизация в GitHub

Различия между `env`, repository/org/environment variables, secrets и contexts; области видимости, наследование, маскирование в логах и практики именования.

ruЭнциклопедия33 урока6 модулей9
GitHub actionsАвтоматизация в GitHubМодель workflowсостояние и артефактыматрицы и runtimeрелизы и environmentsПереиспользование и собственные actionsgovernance и эксплуатацияgithubactions

Variables, env и secrets

Различия между `env`, repository/org/environment variables, secrets и contexts; области видимости, наследование, маскирование в логах и практики именования.

Variables, env и secrets

В GitHub Actions есть несколько похожих, но не взаимозаменяемых способов хранить значения: env, configuration variables (vars), secrets и contexts. Все они выглядят как «переменные», но отвечают за разные задачи. Ошибка обычно начинается там, где одно и то же значение сначала кладут в repository secret, потом дублируют в env, потом пытаются прочитать его через ${{ github.* }} и удивляются, почему оно видно не там, где ожидалось.

Короткая практическая граница такая: env — это переменные окружения для команд и actions во время выполнения job; vars — несекретная конфигурация, заданная в настройках repository, organization или environment; secrets — чувствительные значения, которые GitHub маскирует в логах и не показывает обратно после сохранения; contexts — объекты данных, через которые expression engine подставляет значения в YAML до или во время выполнения отдельных частей workflow. Эта статья продолжает модель из Workflow: файл, запуск, jobs и steps, Contexts и expressions и Permissions и GITHUB_TOKEN: теперь важен не только синтаксис, но и место, где значение появляется.

flowchart TD A[Где хранится значение?] --> B[env в workflow/job/step] A --> C[vars в organization/repository/environment] A --> D[secrets в organization/repository/environment] A --> E[contexts: github, matrix, needs, steps] B --> B1[Runtime-переменные для shell и actions] C --> C1[Несекретная конфигурация через ${{ vars.NAME }}] D --> D1[Чувствительные значения через ${{ secrets.NAME }}] E --> E1[Данные expression engine для if, with, env, concurrency] B1 --> F[Step получает environment variables] C1 --> F D1 --> F E1 --> G[YAML-решения до/во время выполнения job] F --> H[run: npm test / deploy / publish] G --> H
flowchart TD
  A[Где хранится значение?] --> B[env в workflow/job/step]
  A --> C[vars в organization/repository/environment]
  A --> D[secrets в organization/repository/environment]
  A --> E[contexts: github, matrix, needs, steps]

  B --> B1[Runtime-переменные для shell и actions]
  C --> C1[Несекретная конфигурация через ${{ vars.NAME }}]
  D --> D1[Чувствительные значения через ${{ secrets.NAME }}]
  E --> E1[Данные expression engine для if, with, env, concurrency]

  B1 --> F[Step получает environment variables]
  C1 --> F
  D1 --> F
  E1 --> G[YAML-решения до/во время выполнения job]

  F --> H[run: npm test / deploy / publish]
  G --> H
Четыре источника значений в workflow: `env` работает как окружение процесса, `vars` и `secrets` приходят из настроек, contexts питают expression engine.

env: значение для процесса

env задаёт environment variables, которые увидит shell-команда или action. Его можно объявить на уровне workflow, job или step. Чем ближе уровень к step, тем уже область видимости и тем выше приоритет при совпадении имени.

name: CI

on: [push]

env:
  NODE_ENV: test
  APP_NAME: billing-api

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: postgres://postgres:postgres@localhost:5432/app_test
    steps:
      - uses: actions/checkout@v4

      - name: Print runtime env
        run: |
          echo "APP_NAME=$APP_NAME"
          echo "NODE_ENV=$NODE_ENV"

      - name: Override only this step
        env:
          NODE_ENV: production-like
        run: echo "NODE_ENV=$NODE_ENV"

Внутри run удобнее читать $NODE_ENV, $DATABASE_URL, $APP_NAME, потому что это уже обычные переменные окружения runner. А вот в YAML-полях, которые обрабатывает GitHub expression engine, обычно используют ${{ env.NODE_ENV }}. Это две разные стадии чтения: expression ${{ ... }} подставляется GitHub Actions, а $NAME раскрывает shell на runner.

steps:
  - name: Use env in expression and shell
    if: ${{ env.NODE_ENV == 'test' }}
    run: echo "Shell sees NODE_ENV=$NODE_ENV"

Хорошее правило: env подходит для runtime-настроек конкретного workflow: режим тестов, путь к build-директории, флаги CLI, временные значения, которые не жалко показать в логах. Для передачи вычисленного значения из одного step в следующий чаще лучше использовать environment files, например GITHUB_ENV, а для структурированной передачи результата — Step outputs и job outputs.

vars: конфигурация без секрета

Configuration variables задаются не прямо в YAML, а в Settings репозитория, организации или environment. В workflow они читаются через vars context:

name: Deploy preview

on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: preview
    env:
      PUBLIC_API_BASE_URL: ${{ vars.PUBLIC_API_BASE_URL }}
      FEATURE_FLAGS: ${{ vars.FEATURE_FLAGS }}
    steps:
      - run: |
          echo "Deploying to $PUBLIC_API_BASE_URL"
          echo "Flags: $FEATURE_FLAGS"

vars хорошо подходят для значений, которые являются конфигурацией, но не секретом: публичный base URL, имя cloud region, имя package registry, feature flag, project id, имя S3 bucket, если само имя не раскрывает ничего опасного. Если значение можно спокойно написать в README, его обычно можно хранить в vars, а не в secrets.

У vars есть важный плюс для governance: одну и ту же переменную можно задать на уровне organization, переопределить в repository и отдельно настроить для environment вроде staging или production. Это особенно удобно вместе с Environments и protection rules: один workflow может использовать одинаковый YAML, но получать разные URL, регионы или имена ресурсов в зависимости от выбранного environment.

secrets: чувствительные значения

Secrets нужны для значений, которые нельзя раскрывать в логах и PR: API tokens, webhook secrets, private keys, passwords, signing keys. В workflow они доступны через secrets context:

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Publish package
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

GitHub маскирует secrets в логах, но маскирование не надо считать криптографической защитой от любого вывода. Если секрет преобразовать, склеить с другими строками, закодировать, распечатать частями или положить в structured JSON, GitHub может не распознать все варианты. Поэтому в Secure use и threat model secrets рассматриваются как данные, которые надо не только маскировать, но и не передавать лишнему коду.

Есть ещё один практический момент: secrets недоступны в workflow runs из forks, кроме GITHUB_TOKEN с ограничениями, и не передаются в Dependabot events так же, как в обычные trusted runs. Это сделано специально: untrusted PR не должен получить production token. Если workflow должен проверять внешние PR, держите test job без secrets, а privileged job отделяйте условиями, approvals или environment protection rules. Для опасных сценариев рядом стоят pull_request_target и untrusted PR и Runner governance.

Для cloud deploy современная норма — не хранить long-lived cloud access key в secrets, если provider поддерживает OIDC. Тогда workflow получает короткоживущий token через id-token: write, а доверие настраивается на стороне cloud provider. Это тема Cloud deploy через OIDC и OIDC и секреты облаков, но граница уже видна здесь: GitHub secret — запасной вариант для долгоживущей тайны, OIDC — предпочтительный путь для облачных credentials.

Contexts: не все значения живут в shell

Contexts — это объекты, из которых GitHub Actions берёт данные для expressions: github, env, vars, secrets, job, steps, runner, matrix, needs, inputs и другие. Они особенно важны в if, with, env, name, run-name, concurrency и других YAML-полях, которые поддерживают expressions.

jobs:
  test:
    runs-on: ubuntu-latest
    if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' }}
    env:
      CI_BRANCH: ${{ github.ref_name }}
      CACHE_PREFIX: ${{ vars.CACHE_PREFIX }}
    steps:
      - run: echo "Running on $CI_BRANCH"

Главная ловушка: context expression и shell variable не одно и то же. В if нельзя написать if: $NODE_ENV == 'test'; GitHub не запускает shell, чтобы решить, выполнять ли step. Надо писать ${{ env.NODE_ENV == 'test' }}. А внутри run наоборот естественнее использовать $NODE_ENV, потому что команда уже выполняется на runner.

# Неверно: это поле читает GitHub Actions, а не shell.
if: $NODE_ENV == 'test'

# Верно: expression engine читает env context.
if: ${{ env.NODE_ENV == 'test' }}

Ещё одна ловушка — доступность contexts зависит от места в YAML. Например, secrets нельзя свободно использовать в каждом поле workflow, а steps.<id>.outputs появляется только после выполнения step с этим id. Когда значение нужно передать между jobs, не пытайтесь «дотянуться» до environment variable другого job: jobs изолированы. Используйте Step outputs и job outputs, Artifacts и reports или Dependency cache — в зависимости от задачи.

Наследование и приоритеты

Для env ближайший scope выигрывает: step переопределяет job, job переопределяет workflow. Это удобно, но может прятать ошибки, если везде использовать одинаковые имена вроде TOKEN, URL, ENV. Лучше писать имена с доменом смысла: NODE_AUTH_TOKEN, PUBLIC_API_BASE_URL, AWS_REGION, DEPLOY_ENV, TEST_DATABASE_URL.

env:
  DEPLOY_ENV: staging

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DEPLOY_ENV: production
    steps:
      - run: echo "$DEPLOY_ENV" # production

Для vars и secrets приоритет зависит от уровня, где они заданы: organization, repository, environment. Environment-level значения применяются только к job, который объявил environment. Это не просто namespace; для environment secrets GitHub сначала применяет protection rules. Иными словами, job не должен получить production secret до approval, если environment требует approval.

jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy with production secret
        run: ./deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Этот фрагмент выглядит коротко, но в нём спрятана важная эксплуатационная идея: YAML не обязан знать сам production token. Он только говорит: «для job нужен environment production». А уже environment решает, какие vars/secrets доступны и какие protection rules надо пройти.

Практики именования и хранения

Не кладите секреты в env на верхнем уровне workflow. Технически можно написать env: TOKEN: ${{ secrets.X }}, но тогда secret становится доступен всем jobs и steps этого workflow, включая сторонние actions. Лучше передавать secret ровно в тот step, который его использует.

# Плохо: secret попадает во все jobs и steps этого workflow.
env:
  PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}

# Лучше: secret доступен только конкретному step.
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Publish metadata
        run: ./scripts/publish-metadata.sh
        env:
          PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}

Не используйте secrets как склад любой конфигурации. Когда всё лежит в secrets, workflow становится менее читаемым: непонятно, какие значения реально чувствительные, а какие просто настройки окружения. Публичный URL в secret не становится безопаснее; он просто становится менее видимым для ревью.

Не печатайте contexts целиком. toJSON(github) полезен для debug, но contexts могут содержать чувствительные или шумные поля. Для troubleshooting лучше выводить конкретные безопасные значения и оформлять полезный результат через Workflow commands и summaries.

Удобный naming baseline:

PUBLIC_*          несекретные значения, которые могут попасть в клиент или логи
*_TOKEN           чувствительный токен, обычно secret
*_URL             URL; secret только если сам URL содержит credential
*_REGION          vars или env
*_ENV             имя окружения: staging, production, preview
TEST_*            значения только для CI/test job

Мини-пример: один workflow, три типа данных

Ниже workflow показывает нормальное разделение: vars задаёт публичную конфигурацию, env — runtime-флаги, secrets — токен только для publish step.

name: Build and publish

on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write

env:
  NODE_ENV: production
  DIST_DIR: dist

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      PUBLIC_API_BASE_URL: ${{ vars.PUBLIC_API_BASE_URL }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
        env:
          VITE_API_BASE_URL: ${{ env.PUBLIC_API_BASE_URL }}
      - run: tar -czf app.tgz "$DIST_DIR"

  publish:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Publish artifact metadata
        run: ./scripts/publish-metadata.sh
        env:
          PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}

У этого примера есть намеренное упрощение: файл app.tgz не попадёт из build в publish, потому что jobs изолированы. Для настоящего handoff нужен artifact из Artifacts и reports. Зато разделение данных здесь правильное: несекретная конфигурация идёт через vars, runtime-настройки — через env, чувствительный token — только в один step.

See also

Быстрое повторение #rc-1

В workflow есть несекретный публичный base URL API, который отличается для staging и production. Куда его лучше положить: env, vars или secrets, и почему?

Показать ответ

В vars: это несекретная конфигурация, которую можно задавать на уровне org/repository/environment и читать как ${{ vars.PUBLIC_API_BASE_URL }}. secrets тут только ухудшат читаемость, а env лучше оставить для runtime-настроек конкретного workflow/job/step.

Источники

  1. GitHub Docs: Store information in variables
  2. GitHub Docs: Contexts reference
  3. GitHub Docs: Using secrets in GitHub Actions
  4. GitHub Docs: Deployments and environments