Есть знания, которые можно получить по учебнику. А есть те, которые впечатываются в память через настоящий провал — через ночные часы и сообщения "deploy failed" в три часа ночи.

Мой первый опыт с CI/CD — из второй категории.

Что я хотела автоматизировать

Небольшой веб-проект: VPS, GitHub, фронтенд на Node.js. Пуш в main — и код автоматически билдится, тесты проходятся, файлы улетают на сервер. Без ручных ssh-подключений и "а ты не забыла запустить сборку?". Казалось, задача на пару часов максимум.

Базовая структура:

  • GitHub как репозиторий
  • VPS как сервер
  • Rsync для передачи файлов
  • GitHub Actions как оркестратор

Первая попытка: build-скрипт

Начала с простого bash-скрипта. Установить зависимости, собрать проект, запушить на сервер. Скрипт работал — пока я запускала его вручную из терминала. Но хотелось автоматизации. Очевидный следующий шаг — GitHub Actions.

Второй заход: GitHub Actions

Написала вполне разумный workflow:

on: push to main
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: npm install && npm run build
      - name: Deploy
        run: rsync -avz --delete dist/ user@server:/var/www/app/

Выглядело логично. На бумаге — идеально. В реальности первый же пуш показал все подводные камни.

Ошибка 1: rsync не установлен на runner

Первый же раннер GitHub Actions сообщил: "rsync: command not found". Как — не установлен? В моём терминале он есть, а в облаке — нет. ubuntu-latest это минимальный образ, без rsync.

Исправление: добавить шаг установки apt-get install rsync перед деплоем.

Ошибка 2: SSH-ключи и known_hosts

Дальше — попытка подключиться к серверу по SSH. Failed: "Host key verification failed". GitHub Actions runner не знает мой сервер. Пришлось добавить его в known_hosts.

Исправление:

- name: Add server to known_hosts
  run: |
    ssh-keyscan -H $SERVER_IP >> ~/.ssh/known_hosts
  env:
    SERVER_IP: ${{ secrets.SERVER_IP }}

Ошибка 3: Rsync и права

Снова failed. Rsync не мог удалить файлы на сервере — не хватало прав записи. Я использовала пользователя deploy, но права на папку принадлежали www-data.

Исправление: chown -R deploy:www-data /var/www/app и выставить правильные права на папку.

Ошибка 4: Артефакты между шагами

С шестой попытки workflow наконец прошёл. Но оказалось, что артефакты после билда не сохраняются между шагами — каждый шаг запускается в чистой среде. Мой собранный проект исчез.

Исправление: использовать actions/upload-artifact и actions/download-artifact для сохранения результата билда между шагами.

Что я поняла в итоге

Четыре вещи, которые я усвоила после этого опыта:

1. Rsync в GitHub Actions — не тривиальная задача. Лучше использовать готовые экшены типа appleboy/scp-action или easingthemes/ssh-deploy. Они решают все грабли с SSH и known_hosts за тебя.

2. Права на сервере нужно настраивать до первого деплоя. Не после. Создать пользователя, добавить в группу www-data, выставить umask так, чтобы файлы создавались с нужными правами.

3. Тесты — до деплоя. Звучит очевидно. Но у меня сначала деплоилось, потом CI/CD сламывался на этапе тестов. Я откатывала деплой, правила тесты, пушила снова. Лучше один раз поставить тесты в начало пайплайна.

4. Отдельная ветка для staging. Пушить напрямую в main и сразу деплоить на production — плохая практика. Правильно: пуш в develop → автодеплой на staging → пуш в main → ручное подтверждение или автодеплой на production.

Итоговый рабочий пайплайн

Вот что в итоге работает надёжно:

  • actions/checkout
  • setup-node
  • npm ci && npm run build
  • npm test (критично — до деплоя)
  • upload-artifact
  • appleboy/scp-action с приватным ключом из secrets

Звучит просто. Но между "звучит просто" и "работает надёжно" — четыре часа отладки, три сломанных деплоя и много нервных сообщений в чат.

Теперь я каждый раз настраиваю CI/CD с нуля — и каждый раз это занимает не пять часов, а час. Потому что все грабли уже встретила.

DevOps учат не книги. Его учат failure-режимы. Хорошая новость: каждый сломанный деплой — это одна новая строка в резюме.