Короткий ответ: деплой через GitHub Actions — это workflow-файл в .github/workflows/, который запускается при пуше в ветку и по SSH выполняет на сервере команды обновления. Секреты (адрес, пользователь, ключ) хранятся в настройках репозитория и в файл не попадают. Сборку обычно делают в самом CI, а на сервер доставляют готовый результат — это экономит ресурсы сервера и сокращает время простоя.
Смежные темы: сервисы systemd, SSH-ключи и управление Node.js через PM2.
Что даёт автоматический деплой
| Ручной деплой | Автоматический |
|---|---|
| Забытые шаги: не выполнили сборку, не перезапустили сервис | Последовательность команд одна и та же каждый раз |
| Нет записи, что именно и когда выкатывалось | История запусков с результатом каждого |
| Правки прямо на сервере, расхождение с репозиторием | Сервер — только результат, источник один |
| Откат — вручную и по памяти | Откат повторным запуском предыдущей версии |
Главная выгода не в скорости, а в предсказуемости: одинаковые команды выполняются одинаково, и человеческий фактор из процесса уходит.
Подготовка секретов
В репозитории: Settings → Secrets and variables → Actions. Нужны как минимум:
| Секрет | Значение |
|---|---|
SSH_HOST |
Адрес сервера |
SSH_USER |
Пользователь для деплоя |
SSH_KEY |
Приватный ключ целиком, включая строки BEGIN и END |
SSH_PORT |
Порт SSH, если он не 22 |
Для деплоя стоит завести отдельного пользователя, а не использовать root. Ему нужны права только на каталог приложения и на перезапуск конкретного сервиса — это делается через правило sudoers на одну команду.
Файл workflow
Создайте .github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [ main ]
workflow_dispatch:
concurrency:
group: deploy-production
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Build
run: |
npm ci
npm run build
- name: Deploy over SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
set -e
cd /var/www/app
git fetch --all
git reset --hard origin/main
npm ci --omit=dev
pm2 reload app
echo "deployed $(git rev-parse --short HEAD)"
Разбор ключевых мест:
| Фрагмент | Зачем |
|---|---|
concurrency |
Не даёт двум деплоям выполняться одновременно |
workflow_dispatch |
Позволяет запустить деплой вручную из интерфейса |
npm ci |
Устанавливает строго по lock-файлу, в отличие от npm install |
set -e |
Останавливает выполнение при первой ошибке вместо продолжения |
git reset --hard |
Приводит сервер к состоянию репозитория, убирая локальные правки |
pm2 reload |
Перезапуск без разрыва соединений, в отличие от restart |
Строка git reset --hard уничтожает изменения, сделанные прямо на сервере. Это и есть цель: источник истины — репозиторий. Если на сервере есть файлы, которых нет в git (например, конфигурация с секретами), их нужно вынести за пределы каталога деплоя.
Где собирать
| Вариант | Плюсы | Минусы |
|---|---|---|
| Сборка в CI, доставка готового | Сервер не нагружается, простой минимален | Нужен способ доставки артефакта |
| Сборка на сервере | Проще настроить | Требует ресурсов, сервер занят во время сборки |
| Образ контейнера | Окружение совпадает с локальным | Требует реестра образов и работы с Docker |
Для небольших проектов сборка на сервере приемлема. На слабом VPS сборка фронтенда может занять всю память — тогда её выносят в CI.
Безопасность
- Ключ только для деплоя. Отдельная пара ключей, не та, которой вы заходите руками.
- Ограничение по команде. В
authorized_keysможно указатьcommand=— тогда ключ позволит выполнить только разрешённый скрипт. - Секреты не в логах. GitHub маскирует значения секретов в выводе, но строка, попавшая в переменную окружения и напечатанная, может утечь.
- Ветка защищена. Деплой по пушу в
mainбезопасен только тогда, когда вmainнельзя запушить напрямую. - Доступ к репозиторию. Тот, кто может запушить в ветку деплоя, может выполнить код на сервере.
Последний пункт часто недооценивают: автоматический деплой означает, что право записи в репозиторий равно праву выполнять команды на сервере.
Откат
# на сервере: вернуться на предыдущий коммит
cd /var/www/app
git log --oneline -10
git reset --hard <нужный-коммит>
npm ci --omit=dev
pm2 reload app
Для быстрого отката полезно, чтобы workflow принимал параметр с нужной ревизией. Тогда возврат к прежней версии выполняется кнопкой из интерфейса, а не через SSH.
