GitHub Actions — CI/CD рецепты

Приветствую! Здесь вы наверняка найдете, что ищете. Примеры в лаборатории рассчитаны на то, что мы разбираем что-то конкретное.

Текущая статья посвящена cI для Node.js и Python, тесты, деплой на GitHub Pages, секреты и matrix. Пошаговый разбор каждой строки workflow для курсовой, лабораторной и pet-проекта..

Поэтому за теорией по текущей теме вам — в энциклопедию. Если ещё не погружались, то маршрут прост:

  1. Основы
  2. Система и сеть
  3. Данные и разметка
  4. Код и разработка
  5. Языки
  6. Искусственный интеллект
  7. Проект
  8. Инфраструктура и безопасность
  9. Спин-офф

Обязательно пройдитесь.

А теперь приступим к нашему предмету.

Теория и соседние материалы

Системное введение — GitHub Actions.

Полный справочник по ключам YAML — Справочник по GitHub Actions.

Связка Git и триггеров — Git и GitFlow в DevOps.

Пошаговый деплой сайта — кейс GitHub Pages.


  1. Прочитайте блок «Что такое GitHub Actions» — там смысл CI/CD без жаргона.
  2. Сделайте первый workflow за 5 минут — минимальный рабочий файл.
  3. Найдите свой стек (Node, Python, деплой, PR).
  4. Скопируйте YAML целиком.
  5. Прочитайте Разбор и Разбор построчно под кодом.
  6. Выполните Попробуйте — одна маленькая правка закрепляет тему.
  7. Для отчёта по лабораторной можно скриншотить вкладку Actions и зелёный step.

Что такое GitHub Actions простыми словами

Вы делаете git push — код попадает на GitHub. GitHub Actions — это робот на сервере GitHub, который автоматически выполняет команды из вашего репозитория: установить Node.js, запустить тесты, собрать сайт, залить на GitHub Pages.

Инструкции роботу лежат в файле YAML (текст с отступами) в папке .github/workflows/.

Без CI С GitHub Actions
«У меня локально всё работает» На каждый push проверка на чистой Linux-ВМ
Преподаватель клонирует и мучается с версией Node В YAML зафиксирована версия 20
Забыли запустить тесты перед сдачей Тесты стартуют сами, результат виден в PR
Деплой — копировать папку руками npm run build + публикация на Pages одной кнопкой
flowchart LR
  A[Вы: git push] --> B[GitHub видит push]
  B --> C[Читает .github/workflows/ci.yml]
  C --> D[Поднимает ubuntu-latest]
  D --> E[checkout → npm ci → npm test]
  E --> F{Тесты OK?}
  F -->|да| G[✓ зелёная галочка]
  F -->|нет| H[✗ красный крест в PR]

CI (Continuous Integration) — «каждое изменение кода автоматически проверяется». CD (Continuous Delivery/Deployment) — «после проверок артефакт выкладывается на сервер или Pages».


Словарь за 30 секунд

Термин Простыми словами
Workflow Один YAML-файл = один сценарий автоматизации
Job Блок работы на одной виртуальной машине
Step Один шаг внутри job — команда или готовый action
Runner Временная ВМ, где выполняются step (ubuntu-latest = Linux)
Action Чужой готовый step из каталога (actions/checkout@v4)
on: Когда запускать — push, PR, расписание, кнопка
uses: Подключить action
run: Выполнить shell-команду (npm test, pytest)
with: Параметры для action (версия Node, путь к папке)
${{ … }} Подстановка переменной GitHub (секрет, matrix, ref)
Secret Пароль/токен в Settings, в код не пишется
Artifact Zip с build/ или dist/ — скачать из UI run
Matrix Один job × много версий (Node 18 и 20 параллельно)

Первый workflow за 5 минут

Задача: с нуля получить зелёный run во вкладке Actions.

Шаг 1 — создать файл локально

В корне репозитория (там же, где README.md):

my-project/
  .github/
    workflows/
      hello.yml    ← ваш первый workflow
  README.md

Папки .github и workflows с точкой — это стандарт GitHub, не опечатка.

Шаг 2 — минимальный YAML

Файл .github/workflows/hello.yml:

name: Hello CI

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - run: echo "GitHub Actions работает"

Шаг 3 — отправить на GitHub

git add .github/workflows/hello.yml
git commit -m "ci: первый workflow GitHub Actions"
git push origin main

Если основная ветка называется master, в YAML замените main на master.

Шаг 4 — посмотреть результат

  1. Откройте репозиторий на github.com.
  2. Вкладка Actions → слева Hello CI → сверху последний run.
  3. Клик по job greet → step с echo → в логе строка GitHub Actions работает.
  4. Зелёная галочка ✓ — workflow успешен.

Что увидите в UI:

Элемент Значение
Жёлтый кружок Run ещё выполняется
Зелёная галочка Все step прошли
Красный крест Какой-то step упал — откройте его лог
Re-run jobs Повторить без нового коммита

Попробуйте: на вкладке Actions нажмите Run workflow (работает, потому что в YAML есть workflow_dispatch).


Анатомия YAML-файла workflow

Любой workflow — это четыре уровня вложенности. Отступы только пробелами (обычно 2), табы ломают парсер.

name          ← имя в UI
on            ← триггеры
jobs          ← список job
  job_id      ← имя job (вы придумываете)
    runs-on   ← ОС runner
    steps     ← список шагов
      - run   ← команда
      - uses  ← action

Разбор минимального hello.yml построчно

name: Hello CI

Разбор:

  • name: — подпись workflow во вкладке Actions. Можно по-русски: name: Моя первая CI.
  • Без name GitHub покажет имя файла hello.yml.

on:
  push:
    branches: [main]
  workflow_dispatch:

Разбор построчно:

Строка Смысл
on: Блок «когда запускать»
push: При отправке коммитов (git push)
branches: [main] Только push в ветку main, push в feature/login этот workflow не тронет
workflow_dispatch: Ручной запуск кнопкой Run workflow в UI

Короткая запись того же триггера: on: [push, workflow_dispatch] — без фильтра по ветке (сработает на любой push).


jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - run: echo "GitHub Actions работает"

Разбор построчно:

Строка Смысл
jobs: Начало списка job. Можно несколько: test, deploy, …
greet: ID job — латиница, без пробелов. В логах и в needs: greet
runs-on: ubuntu-latest GitHub выдаёт свежую Ubuntu с bash, curl, git
steps: Шаги выполняются сверху вниз на одной и той же ВМ
- run: echo "…" Минус + пробел = элемент списка. run — команда в shell
echo Печать текста в лог (как в терминале Linux)

Почему - перед step: в YAML это массив. Каждый step — новый элемент списка.


Основы — checkout и actions

Checkout — скачать код на runner

Задача: понять, зачем почти везде первый step — actions/checkout.

Без checkout на runner пустая папка. Команда npm ci упадёт — нет package.json.

steps:
  - uses: actions/checkout@v4
  - run: ls -la
  - run: cat package.json

Разбор построчно:

Строка Смысл
- uses: actions/checkout@v4 uses = взять готовый action. actions/checkout — официальный, клонирует репо
@v4 Версия action. Пишите @v4, @v5, не @main — иначе завтра action может сломаться
ls -la Список файлов в рабочей папке — в логе увидите исходники
cat package.json Печать файла — проверка, что checkout сработал

Где лежит код на runner: переменная $GITHUB_WORKSPACE — обычно /home/runner/work/ИМЯ_РЕПО/ИМЯ_РЕПО.

Попробуйте: добавьте под checkout:

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

Разбор fetch-depth: 0:

  • По умолчанию checkout качает 1 последний коммит (fetch-depth: 1) — быстрее.
  • 0 = вся история Git. Нужно для release notes, git describe, Docusaurus «last updated».

uses: и run: — в чём разница

uses: run:
Что это Готовый action (чужой YAML+скрипт) Ваша команда в shell
Пример actions/setup-node@v4 npm test
Когда Стандартные задачи (git, node, cache) Ваши скрипты проекта

Один step — либо uses, либо run (иногда оба через run внутри action).


Синтаксис ${{ }} — подстановка значений

GitHub подставляет значения до выполнения step:

node-version: ${{ matrix.node-version }}
github_token: ${{ secrets.GITHUB_TOKEN }}
if: github.ref == 'refs/heads/main'
Выражение Откуда значение
${{ matrix.node-version }} Из блока strategy.matrix
${{ secrets.API_TOKEN }} Settings → Secrets
${{ github.ref }} Текущая ветка или тег
${{ github.event_name }} push, pull_request, …
${{ runner.os }} Linux, Windows, macOS

В логах секреты заменяются на ***. Писать echo ${{ secrets.TOKEN }} бессмысленно — увидите звёздочки.


Стартовые рецепты

Простые сценарии для отчёта, курсовой и первого badge в README.

Node.js — установка и тесты (самый частый запрос)

Задача: на каждый push и pull request установить зависимости и запустить npm test.

Файл .github/workflows/node-ci.yml:

Разбор блоков:

Блок Зачем
pull_request + push PR проверяется до merge; push в main — после слияния
setup-node@v4 Ставит Node.js 20 на runner (локально у вас может быть 22 — в CI всегда 20)
cache: 'npm' Кэш скачанных пакетов — второй run быстрее на 30–60 секунд
npm ci clean install строго по package-lock.json. Для CI предпочтительнее npm install
npm test Вызывает скрипт "test" из package.json

Разбор построчно — блок on::

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  • push на ветку feature/x без открытого PR — только если добавите push без фильтра или отдельный workflow.
  • pull_request с branches: [main] — когда PR влит в main или открыт в main (target branch = main).

Связь с package.json — в проекте должен быть скрипт "test":

{
  "scripts": {
    "test": "jest"
  }
}

Другие варианты той же строки: "node --test", "vitest run", "mocha". Главное — чтобы npm test находил команду.

Без "test" команда npm test упадёт с missing script: test.

Что увидите в логе step npm test:

> my-app@1.0.0 test
> jest

 PASS  src/app.test.js
Tests: 3 passed, 3 total

Красный step — прокрутите вверх до первой строки FAIL или Error:.

Попробуйте:

  1. Добавьте step - run: npm run lint после npm ci.
  2. Добавьте в README badge (замените USER и REPO):
![CI](https://github.com/USER/REPO/actions/workflows/node-ci.yml/badge.svg)

Python — pytest

Задача: прогнать unit-тесты на Python 3.12 в CI.

Разбор построчно:

Строка Смысл
on: [push, pull_request] Короткая форма — любой push и любой PR
setup-python@v5 Python 3.12 на runner
cache: 'pip' Кэш wheel-файлов pip
pip install -r requirements.txt Зависимости проекта из файла
pip install pytest Тестовый раннер (если нет в requirements)
pytest -q quiet — меньше строк в логе, только итог и ошибки

Структура тестов для pytest:

project/
  src/
    calc.py
  tests/
    test_calc.py

Пример tests/test_calc.py:

def test_add():
    assert 1 + 1 == 2

Попробуйте: замените pytest -q на pytest -v — в логе будет имя каждого теста (удобно для отчёта).

Если зависимости в pyproject.toml:

      - run: pip install -e ".[dev]"
      - run: pytest -q

Только pull request — проверки без деплоя

Задача: на каждый PR в main — lint и test; деплой этот файл не трогает.

Разбор:

  • Триггер только pull_request — обычный push в feature-ветку без PR workflow не запустит.
  • В карточке PR появится секция Checks — зелёный или красный статус.
  • Если в Settings → Branches → Branch protection включено «Require status checks», merge без зелёного CI невозможен.

Что увидите в PR:

All checks have passed
  ✓ verify / test (pull_request)

Попробуйте: намеренно сломайте тест, откройте PR — преподаватель увидит красный крест до исправления.


Java — Maven (сборка и тесты)

Задача: типичный CI для учебного Java-проекта с pom.xml.

Разбор построчно:

Строка Смысл
setup-java@v4 JDK на runner
distribution: temurin Сборка Eclipse Temurin (бывший AdoptOpenJDK)
java-version: '21' Версия языка — как в вашем pom.xml
cache: maven Кэш ~/.m2 — не качать одни и те же jar каждый раз
mvn -B verify Batch mode (без интерактива) + compile + test + package checks

Попробуйте: Gradle вместо Maven — cache: gradle и run: ./gradlew test.


CI/CD рецепты

1. Matrix — Node 18 и 20 параллельно

Задача: убедиться, что проект работает на двух LTS-версиях Node.

Разбор построчно:

Строка Смысл
strategy: Настройки параллельного запуска
matrix: Таблица параметров → GitHub создаёт отдельный run на каждую комбинацию
node-version: [18, 20] Два job: один с Node 18, второй с 20
fail-fast: false Упал тест на 18 — проверка на 20 всё равно дойдёт до конца
${{ matrix.node-version }} В первом job подставится 18, во втором 20

Что увидите в UI: два под-job test (18) и test (20) внутри одного workflow run.

Попробуйте — кросс-платформа:

    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node-version: [20]
    runs-on: ${{ matrix.os }}

Получится 2 job: Linux + Windows.


2. Кэш npm вручную (actions/cache)

Задача: ускорить CI в монорепо, когда встроенного cache: 'npm' мало.

Фрагмент steps:

      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        id: npm-cache
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-
      - run: npm ci

Разбор построчно:

Строка Смысл
actions/cache@v4 Сохранить/восстановить папку между run
id: npm-cache Имя step — можно сослаться в if: steps.npm-cache.outputs.cache-hit
path: ~/.npm Что кэшируем — каталог npm на Linux
hashFiles('**/package-lock.json') Хэш lock-файла в ключе: изменился lock → новый кэш
restore-keys: Если точного ключа нет — взять ближайший префикс ${{ runner.os }}-npm-
| после restore-keys: Многострочный YAML — вторая строка с отступом

Что увидите в логе cache step:

Cache restored from key: Linux-npm-a1b2c3d4..

или при первом run:

Cache not found for input keys: Linux-npm-..

Попробуйте: второй push без изменения package-lock.json — время step npm ci обычно падает.


3. Секреты — API-токен без утечки в Git

Задача: вызвать внешний API из CI, токен хранится только в Settings.

Шаг 1 — в GitHub UI:

Settings → Secrets and variables → Actions → New repository secret

Поле Значение
Name API_TOKEN
Secret ваш токен (строка)

Шаг 2 — в YAML:

      - name: Deploy with token
        env:
          API_TOKEN: ${{ secrets.API_TOKEN }}
        run: |
          curl -sf -H "Authorization: Bearer $API_TOKEN" \
            https://api.example.com/deploy

Разбор построчно:

Строка Смысл
name: Deploy with token Подпись step в UI (необязательно, но удобно)
env: Переменные окружения только для этого step
API_TOKEN: ${{ secrets.API_TOKEN }} GitHub подставит секрет перед запуском shell
run: | Многострочный shell-скрипт
curl -sf -s тихо, -f exit code ≠ 0 при HTTP 4xx/5xx — step станет красным
$API_TOKEN Shell-переменная из env (без ${{ }} внутри run)

GITHUB_TOKEN — секрет создаётся автоматически на каждый run. Нужен для push в тот же репозиторий (ветка gh-pages, releases). Пишут ${{ secrets.GITHUB_TOKEN }}.

Секрет в истории Git

Токен в коммите остаётся в истории навсегда.

Отзовите ключ, очистите историю (`git filter-repo` / BFG), создайте секрет заново.

В YAML — только `${{ secrets.NAME }}`.

Попробуйте: step run: echo "token length is ${#API_TOKEN}" с секретом в env — в логе число символов, сам токен скрыт ***.


4. Артефакты — сохранить папку build/

Задача: после сборки положить dist/ в zip — скачать из UI без повторной сборки локально.

Разбор связки:

  • needs: build — job deploy стартует после успешного build.
  • download-artifact — распаковывает zip в dist/ на новой ВМ (у каждого job свой runner).

5. Условный деплой только с main

Задача: тесты на всех ветках и PR; выкладка на Pages — только push в main.

Разбор построчно — job deploy:

Строка Смысл
needs: test Ждём зелёный job test
if: github.event_name == 'push' && github.ref == 'refs/heads/main' PR и push в develop → job пропускается (skipped)
permissions: contents: write Без этого GITHUB_TOKEN только читает репо — push в gh-pages упадёт
peaceiris/actions-gh-pages@v4 Community action: коммитит содержимое publish_dir в ветку gh-pages
publish_dir: ./dist Откуда брать HTML/CSS/JS
publish_branch: gh-pages Куда пушить (GitHub Pages читает эту ветку)

Что увидите при PR: job test зелёный, deploy серый Skipped.

Попробуйте: Vite/React часто пишет в dist, Docusaurus — в build. Проверьте локально npm run build и подставьте правильную папку.


6. Официальный деплой GitHub Pages

Задача: опубликовать статику через официальные action deploy-pages (без peaceiris).

Разбор построчно — верх файла:

Строка Смысл
permissions: pages: write Право публиковать на Pages
id-token: write OIDC-токен для безопасной авторизации
concurrency: group: pages Один deploy Pages одновременно
cancel-in-progress: true Новый push отменяет старый незавершённый deploy
environment: github-pages Связь с Settings → Environments (опционально protection rules)
url: ${{ steps.deployment.outputs.page_url }} Ссылка на сайт в UI run
configure-pages@v5 Подготовка Pages
upload-pages-artifact Zip для Pages
id: deployment Имя step — ссылка steps.deployment.outputs.page_url
deploy-pages@v4 Финальная публикация
path: . Корень репо = готовый HTML. После сборки укажите path: build

В Settings → Pages источник должен быть GitHub Actions, не «Deploy from a branch».

Попробуйте: перед upload добавьте npm ci, npm run build, смените path на build.


7. Расписание cron — ночной smoke test

Задача: раз в сутки проверить, что API отвечает 200.

Разбор построчно:

Строка Смысл
schedule: Запуск по cron (UTC!)
cron: '0 6 * * *' Каждый день в 06:00 UTC (= 09:00 МСК зимой)
curl -s -o /dev/null Тело ответа выбросить
-w "%{http_code}" Напечатать только код статуса
test "$code" = "200" Shell: если не 200 — exit 1 → красный step

Формат cron (5 полей): минута час день_месяца месяц день_недели

Пример Значение
'0 6 * * *' Каждый день 06:00 UTC
'0 */4 * * *' Каждые 4 часа
'30 8 * * 1-5' Пн–Пт в 08:30 UTC

Расписание работает только на default branch (main).

Попробуйте: workflow_dispatch + Run workflow — отладка без ожидания ночи.


8. Concurrency — отмена старых run

Задача: десять push подряд в PR — не ждать десять одинаковых CI, отменять устаревшие.

Добавьте на уровне workflow (рядом с on:, не внутри job):

Разбор построчно:

Строка Смысл
group: ci-${{ github.workflow }}-${{ github.ref }} Одна «очередь» на пару workflow + ветка/PR
cancel-in-progress: true Новый run убивает предыдущий незавершённый

Что увидите: первый run со статусом Cancelled, последний — актуальный результат.


9. Reusable workflow — один шаблон для всех студентов

Задача: преподаватель держит эталон CI, студенты подключают одной строкой.

Репозиторий org/ci-templates/.github/workflows/node-test.yml:

Разбор:

Ключ Смысл
workflow_call Этот workflow не стартует от push — только когда его вызвали
inputs.node-version Параметр от вызывающего workflow
${{ inputs.node-version }} Значение параметра внутри reusable

В проекте студента .github/workflows/ci.yml:

name: CI

on: [push, pull_request]

jobs:
  call-shared:
    uses: org/ci-templates/.github/workflows/node-test.yml@main
    with:
      node-version: '20'

Разбор вызова:

  • uses: org/repo/path@main@main = ветка шаблона (можно @v1 tag).
  • Один репозиторий — локально: uses: ./.github/workflows/node-test.yml.

10. Docker — проверить сборку Dockerfile

Задача: убедиться, что образ собирается на CI (для отчёта по DevOps).

Разбор построчно:

Строка Смысл
setup-buildx-action Современный движок сборки Docker
build-push-action docker build внутри action
context: . Dockerfile и контекст — корень репо
push: false Не пушить в registry — только проверка сборки
tags: myapp:ci Локальное имя образа на runner

Попробуйте: push: true + docker/login-action + GHCR — для продвинутых проектов; теория — контейнеризация. Сначала убедитесь, что локальный Dockerfile собирается — примеры Node, Python, Go с построчным разбором.


Реальный пример — деплой этого сайта

Workflow it-knowledge-base — Docusaurus на GitHub Pages.

Разбор построчно — каждый step:

Step Что делает Зачем
checkout + fetch-depth: 0 Клон с полной историей Docusaurus показывает дату последнего изменения страницы
setup-node 20 Node LTS на runner Совпадает с engines проекта
npm ci Установка по lock-файлу Воспроизводимая сборка
rm -rf .docusaurus .cache build Удалить старый кэш сборки Лечит «залипший» битый build на runner
npm run build Docusaurus → папка build/ Готовый статический сайт
peaceiris/actions-gh-pages Push build/ в gh-pages GitHub Pages отдаёт сайт читателям
force_orphan: true Ветка gh-pages без истории Только HTML, без исходников
user_name / user_email Автор коммита — bot В history видно github-actions[bot]

Цепочка для отчёта:

push main → Actions deploy.yml → npm run build → ветка gh-pages → spirzen.ru

Подробнее с DNS и Settings — кейс GitHub Pages.


Badge «CI passing» в README

Задача: в README красивая картинка статуса последнего run.

![CI](https://github.com/USER/REPO/actions/workflows/node-ci.yml/badge.svg)

Разбор URL:

Часть Замените на
USER ваш логин GitHub
REPO имя репозитория
node-ci.yml имя файла workflow без пути

Пример для этого репозитория:

![Deploy](https://github.com/Spirzen/it-knowledge-base/actions/workflows/deploy.yml/badge.svg)

Статус обновляется после каждого run. Красный badge = последний run упал.


Частые ошибки — симптом и лечение

Симптом в логе Частая причина Что сделать
Workflow не появился в Actions Файл не в .github/workflows/ Проверьте путь и расширение .yml
yaml syntax error Табы вместо пробелов Переведите отступы на 2 пробела
npm ERR! Missing script: "test" Нет "test" в package.json Добавьте скрипт или замените команду
npm ci can only install with package-lock Нет lock-файла npm install локально, закоммитьте lock
Permission denied при gh-pages Нет permissions: contents: write Добавьте блок permissions
No such file or directory: dist Неверная папка сборки Локально npm run build, посмотрите имя папки
Secret пустой Опечатка secrets.API_TOKEN Имя = как в Settings, регистр важен
Сайт без CSS на Pages Неверный base / homepage Для project site нужен /repo-name/ в конфиге
Два deploy подряд Нет concurrency cancel-in-progress: true
PR без Checks Только push в workflow Добавьте pull_request в on:
Как читать красный лог

Откройте упавший step → прокрутите вверх от ##[error] → найдите первую строку с Error:, FAIL или exit code 1. Часто ошибка в step выше, чем кажется (например, npm ci упал из-за lock-файла).


Шпаргалка — что вставить в отчёт по лабораторной

Краткий текст для пояснительной записки (адаптируйте под свой проект):

В репозитории настроен CI на GitHub Actions. Файл .github/workflows/node-ci.yml описывает pipeline: при push и pull request запускается job на виртуальной машине ubuntu-latest, выполняется checkout кода, установка Node.js 20, команда npm ci для установки зависимостей и npm test для автоматического тестирования. Результат отображается во вкладке Actions и в статусах pull request. Это обеспечивает непрерывную интеграцию (CI) — проверку кода на каждое изменение без ручного запуска тестов на машине разработчика.


Куда дальше

Задача Материал
Теория CI/CD, runner, events GitHub Actions
Все ключи YAML, contexts Справочник
E2E в браузере Playwright и Actions
Git, PR, branch protection Основы Git
Деплой сайта с нуля Кейс GitHub Pages
HTTP в job (curl) curl / fetch — примеры
Bash-скрипты в step Bash — однострочники, production