Зачем автоматизировать рутину (даже если в команде 2 человека)
Представьте: вы работаете в пекарне. Каждое утро вы вручную проверяете свежесть ингредиентов, замешиваете тесто, выпекаете хлеб, проверяете качество, упаковываете и развозите по магазинам. А теперь представьте, что у вас есть конвейер, который делает всё это автоматически - вы только загружаете ингредиенты и нажимаете кнопку.
CI/CD - это такой "конвейер" для программистов. Вместо того чтобы вручную проверять код, запускать тесты, собирать проект и загружать его на сервер - всё это делает автоматика. Каждый раз одинаково, без ошибок, без "ой, забыл запустить тесты".
CI означает Continuous Integration ("непрерывная интеграция") - автоматическая проверка кода при каждом изменении. CD означает Continuous Delivery ("непрерывная доставка") - автоматическая загрузка проверенного кода на сервер.
GitHub Actions - это бесплатный инструмент от GitHub, который позволяет настроить такой конвейер. Давайте разберёмся, как это работает.
Как работает GitHub Actions (простыми словами)
Когда вы загружаете код на GitHub (делаете "push"), GitHub может автоматически запустить любые действия. Вы описываете эти действия в специальном файле формата YAML. Этот файл лежит в папке .github/workflows/ вашего проекта.
Думайте об этом как о "рецепте": вы пишете инструкцию, а GitHub следует ей каждый раз, когда вы загружаете новый код.
# .github/workflows/ci.yml
# Это файл, который описывает ваш "конвейер"
# YAML - это формат файла, похожий на список с отступами
# Каждый отступ означает "вложенность" (как подпункты в списке)
# name - имя вашего конвейера (видно в интерфейсе GitHub)
name: CI Pipeline
# on - КОГДА запускать этот конвейер
# Здесь: при каждом push (загрузке кода) в ветки main или develop
# и при создании Pull Request (запроса на слияние кода) в main
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
# jobs - ЧТО делать (список задач)
jobs:
# "test" - имя задачи (придумываете сами)
test:
# runs-on - на какой виртуальной машине запускать
# ubuntu-latest - бесплатная Linux-машина от GitHub
# Каждый запуск создаёт новую чистую машину с нуля
runs-on: ubuntu-latest
# steps - шаги задачи (выполняются последовательно)
steps:
# Шаг 1: Скачать код вашего проекта на виртуальную машину
# uses - использовать готовое действие (action) от другого разработчика
# actions/checkout@v4 - официальное действие GitHub для скачивания кода
- uses: actions/checkout@v4
# Шаг 2: Установить Node.js нужной версии
# actions/setup-node@v4 - официальное действие для установки Node.js
- uses: actions/setup-node@v4
with:
node-version: 20 # Версия Node.js
cache: 'npm' # Кэшировать скачанные пакеты между запусками
# Шаг 3: Установить зависимости проекта (библиотеки)
# npm ci - устанавливает ТОЧНО те версии, что указаны в lock-файле
# (ci = clean install, чистая установка)
- run: npm ci
# Шаг 4: Проверить код линтером
# Линтер - программа, которая ищет ошибки и проблемы в коде
# Например: неиспользуемые переменные, опечатки, нарушения стиля
- run: npm run lint
# Шаг 5: Проверить типы TypeScript
# TypeScript проверяет, что вы не передаёте число туда,
# где ожидается текст (и другие подобные ошибки)
- run: npm run typecheck
# Шаг 6: Запустить тесты
# Тесты - это маленькие программы, которые проверяют,
# что ваш код работает правильно
- run: npm test
# Шаг 7: Собрать проект
# "Сборка" - это превращение вашего кода в готовый для работы вид
# (минификация, оптимизация, компиляция TypeScript в JavaScript)
- run: npm run build
Что происходит после создания этого файла:
- Вы загружаете код на GitHub (git push)
- GitHub видит файл в
.github/workflows/и запускает конвейер - Создаётся чистая виртуальная машина
- Все шаги выполняются последовательно
- Если любой шаг падает (ошибка линтера, провалился тест, не собирается) - весь конвейер "красный"
- В GitHub вы видите зелёную галочку (всё хорошо) или красный крестик (что-то сломалось)
Кэширование: экономим время и деньги
Каждый раз, когда конвейер запускается, он устанавливает ВСЕ зависимости (библиотеки) с нуля. Если у вас 200 библиотек - это может занять 30-60 секунд. При 20 запусках в день - 10-20 минут впустую.
Кэширование решает эту проблему: если библиотеки не изменились, используем сохранённые с прошлого раза.
# Расширенное кэширование
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # Кэширует скачанные пакеты npm
# Кэшируем установленные библиотеки (node_modules)
- name: Cache node_modules
uses: actions/cache@v4 # Готовое действие для кэширования
id: cache-modules # ID - чтобы ссылаться на результат ниже
with:
path: node_modules # Какую папку кэшировать
# key - уникальный ключ кэша
# hashFiles('package-lock.json') - хеш файла с версиями библиотек
# Если lock-файл не изменился - ключ тот же - используем кэш
# Если изменился (добавили/обновили библиотеку) - кэш сбрасывается
key: modules-${{ hashFiles('package-lock.json') }}
# Устанавливаем библиотеки ТОЛЬКО если кэш не найден
- name: Install dependencies
# if - условие: выполнять шаг только если кэш не сработал
# cache-hit - результат предыдущего шага (true/false)
if: steps.cache-modules.outputs.cache-hit != 'true'
run: npm ci
# Дальше как обычно: линтер, тесты, сборка
- run: npm run lint
- run: npm test
- run: npm run build
Автоматический деплой на сервер
Деплой - это загрузка готового проекта на рабочий сервер, чтобы пользователи увидели изменения. Без автоматизации это выглядит так:
- Вы заходите на сервер через SSH (удалённое подключение)
- Скачиваете новый код
- Устанавливаете зависимости
- Собираете проект
- Перезапускаете приложение
С GitHub Actions всё то же самое, но автоматически:
# .github/workflows/deploy.yml
# Этот файл автоматически загружает код на сервер
name: Deploy to Production
on:
push:
branches: [main] # Деплоим ТОЛЬКО когда код попадает в main
# Почему только main? Потому что main - это "готовый" код
# develop - это "в процессе", его на сервер загружать рано
jobs:
# Сначала проверяем код (тесты, линтер)
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: 'npm' }
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
# Потом деплоим (ТОЛЬКО если тесты прошли!)
deploy:
needs: test # "needs" означает "не запускай, пока test не завершится успешно"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Подключаемся к серверу по SSH и выполняем команды
# SSH - это безопасный способ удалённого подключения к серверу
- name: Deploy via SSH
uses: appleboy/ssh-action@v1 # Готовое действие для SSH
with:
# Эти значения хранятся в "секретах" GitHub (об этом ниже)
# Никто не может увидеть их в коде!
host: ${{ secrets.SSH_HOST }} # IP-адрес сервера
username: ${{ secrets.SSH_USER }} # Имя пользователя на сервере
key: ${{ secrets.SSH_PRIVATE_KEY }} # Приватный SSH-ключ
# script - команды, которые выполнятся на сервере
script: |
cd /var/www/myapp # Переходим в папку проекта
git pull origin main # Скачиваем новый код
npm ci --production # Устанавливаем библиотеки
npm run build # Собираем проект
pm2 restart myapp # Перезапускаем приложение
echo "Deploy завершён: $(date)" # Записываем время деплоя
Секреты: как хранить пароли и ключи
Пароли, API-ключи, SSH-ключи и другую секретную информацию НЕЛЬЗЯ хранить в коде. Если кто-то увидит ваш репозиторий - он получит доступ ко всему.
GitHub предоставляет "секреты" (Secrets) - зашифрованное хранилище для конфиденциальных данных. Вот как их настроить:
- Откройте ваш репозиторий на GitHub
- Settings (Настройки) - Secrets and variables - Actions
- New repository secret (Новый секрет)
- Введите имя (например, SSH_HOST) и значение (например, 123.45.67.89)
# Использование секретов в workflow
# Способ 1: Через переменные окружения
# env - задаёт переменные окружения для всех шагов
env:
# ${{ secrets.DATABASE_URL }} - подставляет значение секрета
# В коде конвейера вы увидите ***, а не реальное значение
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NODE_ENV: production # Обычная переменная (не секрет)
# Способ 2: Разные секреты для разных окружений
# Например, для тестового сервера и боевого сервера
jobs:
deploy-staging:
# environment - указывает набор секретов
environment: staging # Секреты из окружения "staging"
steps:
- run: echo "Деплоим на тестовый сервер"
# secrets.DEPLOY_HOST здесь = staging.example.com
deploy-production:
environment: production # Секреты из окружения "production"
# Можно добавить "required reviewers" - кто-то должен одобрить деплой!
# Это защита от случайного деплоя на боевой сервер
steps:
- run: echo "Деплоим на боевой сервер"
# secrets.DEPLOY_HOST здесь = example.com
Уведомления в Telegram: знаем, что происходит
Настроить уведомления о деплое в Telegram - это как повесить табло в офисе, на котором видно, что происходит с проектом. Команда всегда в курсе: деплой прошёл успешно или что-то сломалось.
# Добавьте эти шаги в конец вашего workflow
# Шаг: отправить уведомление в Telegram
- name: Notify Telegram
# if: always() - отправлять всегда (и при успехе, и при ошибке)
# Без этого шаг не выполнится, если предыдущий упал
if: always()
uses: appleboy/telegram-action@v1 # Готовое действие для Telegram
with:
# TELEGRAM_CHAT_ID - ID чата или группы, куда отправлять
# Чтобы узнать ID: добавьте бота @userinfobot в чат
to: ${{ secrets.TELEGRAM_CHAT_ID }}
# TELEGRAM_BOT_TOKEN - токен бота (получить у @BotFather)
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
format: markdown
message: |
*${{ github.repository }}*
${{ job.status == 'success' && 'Deploy успешен' || 'Deploy ПРОВАЛЕН' }}
Branch: `${{ github.ref_name }}`
Commit: `${{ github.event.head_commit.message }}`
Author: ${{ github.actor }}
[Подробнее](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
# Как настроить Telegram-бота для уведомлений:
# 1. Откройте @BotFather в Telegram
# 2. Отправьте /newbot, придумайте имя
# 3. Скопируйте токен бота - это TELEGRAM_BOT_TOKEN
# 4. Добавьте бота в группу или начните чат с ним
# 5. Откройте https://api.telegram.org/bot<TOKEN>/getUpdates
# 6. Найдите chat.id - это TELEGRAM_CHAT_ID
# 7. Добавьте оба значения в секреты GitHub
Защита от случайного мержа
Branch protection - это правила, которые не дают случайно сломать основной код. Это как турникет на входе: не пропускает тех, кто не прошёл проверку.
Настройка в GitHub: Settings - Branches - Add rule. Выберите ветку "main" и включите:
- Require a pull request before merging - нельзя загружать код в main напрямую. Только через Pull Request (запрос на слияние), который должен одобрить другой разработчик
- Require status checks to pass - Pull Request нельзя слить, пока конвейер CI "красный" (тесты не прошли или код не собирается)
- Require approvals: 1 - нужен хотя бы один одобряющий отзыв от другого разработчика
- Dismiss stale pull request approvals - если после одобрения вы добавили новый код, одобрение сбрасывается и нужно получить его заново
Эти правила кажутся строгими, но они спасают от 90% проблем: случайно сломанный код, непроверенные изменения, "я же только одну строчку поменял".
Matrix builds: тестируем на разных версиях
Если ваш проект должен работать на нескольких версиях Node.js (или другой платформы), можно тестировать на всех версиях одновременно:
# Matrix strategy - запуск тестов на нескольких версиях параллельно
jobs:
test:
runs-on: ubuntu-latest
# strategy.matrix - создаёт несколько параллельных запусков
# В данном случае: три запуска с разными версиями Node.js
strategy:
matrix:
node-version: [18, 20, 22] # Три версии = три параллельных задачи
fail-fast: false # Если одна упала - остальные продолжают работать
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
# ${{ matrix.node-version }} подставляет текущую версию из матрицы
# Первая задача получит 18, вторая - 20, третья - 22
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test
Экономия: отменяем старые запуски
Допустим, вы загрузили код, конвейер запустился. Через минуту вы нашли опечатку и загрузили исправление. Теперь работают ДВА конвейера, но первый уже не нужен - он проверяет устаревший код.
# Отменяем предыдущие запуски при новом push в ту же ветку
# Это экономит время и бесплатные минуты GitHub Actions
# concurrency - группировка параллельных запусков
concurrency:
# group - уникальный ключ группы
# Запуски с одинаковым ключом группируются
group: ${{ github.workflow }}-${{ github.ref }}
# cancel-in-progress - отменить предыдущий запуск,
# если запускается новый с тем же ключом
cancel-in-progress: true
# Теперь если вы сделали 3 push подряд за 5 минут,
# первые два конвейера отменятся, запустится только третий
Полный production-ready конвейер
Вот итоговый файл, который объединяет всё вышесказанное. Скопируйте его в свой проект и адаптируйте под себя:
# .github/workflows/production.yml
# Полный CI/CD конвейер для реального проекта
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
# Отменяем предыдущие запуски при новом push
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# === Этап 1: Проверка качества кода ===
# Запускается при каждом push и pull request
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: 'npm' }
# Кэшируем node_modules
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: modules-${{ hashFiles('package-lock.json') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
# Все проверки
- run: npm run lint # Линтер - ищет проблемы в коде
- run: npm run typecheck # TypeScript - проверка типов
- run: npm test -- --coverage # Тесты + отчёт о покрытии
# === Этап 2: Сборка ===
# Запускается ТОЛЬКО если этап 1 прошёл успешно
build:
needs: quality # Зависимость от предыдущего этапа
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: 'npm' }
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: modules-${{ hashFiles('package-lock.json') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- run: npm run build
# Сохраняем результат сборки как "артефакт"
# Артефакт - это файл или папка, которую можно скачать
# или использовать в следующем этапе
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/ # Папка с результатом сборки
retention-days: 1 # Хранить 1 день (потом автоудаление)
# === Этап 3: Деплой на сервер ===
# Запускается ТОЛЬКО для ветки main И ТОЛЬКО если сборка прошла
deploy:
needs: build
# if - условие: только при push в main (не при pull request!)
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production # Используем секреты из окружения "production"
steps:
# Скачиваем артефакт сборки
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
# Деплоим на сервер через SSH
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/myapp
git pull origin main
npm ci --production
npm run build
pm2 restart myapp
# Уведомляем в Telegram
- name: Notify Telegram
if: always()
uses: appleboy/telegram-action@v1
with:
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
message: |
Deploy ${{ job.status }}: ${{ github.event.head_commit.message }}
Стоимость: сколько это стоит
GitHub Actions щедр на бесплатные минуты:
- Публичные репозитории - бесплатно, без ограничений. Совсем. Навсегда
- Приватные репозитории (бесплатный план) - 2000 минут в месяц
- Приватные (Team) - 3000 минут в месяц
Один типичный запуск CI занимает 2-4 минуты. При 20 запусках в день (активная команда из 3-5 человек) это 40-80 минут в день, или 800-1600 минут в месяц. Укладывается в бесплатный лимит с запасом.
Практические советы
- Начните с малого - не пытайтесь сразу настроить идеальный конвейер. Начните с lint + test + build. Потом добавьте автодеплой. Потом уведомления. Каждый шаг даёт ощутимый результат
- Разделяйте CI и CD - тесты запускаются на каждый push (это CI), деплой - только при слиянии в main (это CD). Не деплойте непроверенный код
- Кэшируйте всё - node_modules, Docker-слои, результаты сборки. Каждая сэкономленная минута складывается
- Используйте GITHUB_TOKEN - GitHub автоматически создаёт токен для каждого запуска. Не создавайте персональные токены, если можно обойтись встроенным
- Настройте уведомления - узнать о сломанном деплое через 5 секунд в Telegram лучше, чем через час от пользователей
- Не пропускайте тесты - "я же только CSS поменял" - знаменитые последние слова перед сломанным продакшеном. Тесты запускаются всегда
Итого
CI/CD через GitHub Actions - это не сложно, не дорого и не "для больших команд". Базовый конвейер настраивается за 30 минут, а экономит часы каждую неделю. Машина не забывает запустить тесты, не деплоит не ту ветку и проверяет код каждый раз одинаково - в отличие от уставшего разработчика в пятницу вечером.
Начните с простого: lint + test + build на каждый Pull Request. Когда привыкнете - добавьте автодеплой на сервер. Потом уведомления в Telegram. Каждый шаг - это меньше ручной работы и меньше шансов сломать продакшен. Хотите настроить CI/CD для вашего проекта или автоматизировать процесс разработки? Напишите нам - настроим конвейер, который работает как часы.