Перейти к содержанию
Innovialabs

Автодеплой на VPS через GitHub Actions и SSH

Практика3 мин

Коротко

Создайте отдельного пользователя и SSH-ключ для деплоя, положите приватный ключ в секреты GitHub, а workflow по push в main подключится к серверу, обновит код, соберёт проект и перезапустит сервис; окружение production защитите правилами.

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

Схема простая: при push в main GitHub Actions запускает job, который по SSH подключается к вашему VPS и выполняет там скрипт деплоя — обновить код, поставить зависимости, собрать и перезапустить приложение. Вам нужны три вещи: ключи доступа, секреты в репозитории и сам workflow.

Шаг 1. Пользователь и ключи на сервере

Не деплойте от root. Создайте отдельного пользователя, например deploy, с доступом только к каталогу приложения.

Ключей понадобится два, и их легко перепутать:

КлючОткуда кудаГде хранится
Ключ для CIGitHub Actions → ваш серверПриватный — в секретах GitHub, публичный — в ~/.ssh/authorized_keys пользователя deploy
Deploy keyВаш сервер → GitHubПриватный — на сервере, публичный — в Settings → Deploy keys репозитория (только чтение)

Deploy key нужен, если репозиторий приватный и сервер сам делает git pull. Создать ключ можно так:

ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./deploy_ci -N ""

Используйте для каждой задачи свой ключ: так его легко отозвать, не затрагивая остальной доступ.

Шаг 2. Секреты репозитория

В Settings → Secrets and variables → Actions добавьте:

  • SSH_PRIVATE_KEY — содержимое приватного ключа для CI;
  • SSH_HOST и SSH_USER — адрес сервера и имя пользователя;
  • SSH_KNOWN_HOSTS — вывод ssh-keyscan your-server.com, лучше сверенный с отпечатком ключа, который вы видите на самом сервере.

Последний пункт часто пропускают и отключают проверку хоста. Так делать не стоит: без неё вы не заметите подмену сервера.

Шаг 3. Скрипт деплоя на сервере

Держите логику деплоя в скрипте на сервере, например /srv/app/deploy.sh:

#!/usr/bin/env bash
set -euo pipefail
cd /srv/app
git pull --ff-only origin main
npm ci
npm run build
sudo systemctl restart app

set -euo pipefail остановит скрипт на первой ошибке, чтобы не перезапускать сервис с недособранным кодом. Чтобы deploy мог перезапускать сервис без пароля, разрешите ему только эту команду через visudo:

deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app

Путь к systemctl проверьте командой which systemctl.

Шаг 4. 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
    environment: production
    steps:
      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts

      - name: Run deploy script
        run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "/srv/app/deploy.sh"
  • concurrency не даёт двум деплоям идти одновременно; cancel-in-progress: false позволяет текущему деплою завершиться.
  • workflow_dispatch добавляет ручной запуск — пригодится для повторного деплоя.
  • Если у вас есть CI-проверки, вынесите их в отдельный job и добавьте needs, чтобы деплой шёл только после зелёных тестов.

Шаг 5. Защита окружения production

Строка environment: production связывает job с окружением. В Settings → Environments для него можно настроить:

  • Required reviewers — деплой ждёт ручного подтверждения ответственного;
  • Wait timer — задержка перед стартом;
  • Deployment branches — деплоить можно только из main;
  • Environment secrets — храните SSH-ключи на уровне окружения, тогда их получит только job с этим окружением.

Доступность части правил для приватных репозиториев зависит от тарифа GitHub.

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

  • Деплой от root или с ключом, у которого есть доступ ко всему серверу.
  • StrictHostKeyChecking=no вместо known_hosts.
  • Нет set -e: сборка упала, а сервис всё равно перезапустился.
  • Локальные правки прямо на сервере, из-за которых git pull --ff-only отказывается работать.

Частые вопросы

Так тоже можно, и это часто лучше: сервер не тратит ресурсы на сборку, а на него попадает уже проверенный артефакт. Передавать его можно через rsync или scp по тому же SSH. Вариант с git pull проще для старта.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.