От git push до сайта

11 мин. чтения

Любой проект — это много мелких правок, часть которых можно делать «между делом». Но мы часто их откладываем, ведь в классическом варианте эти правки нужно не только внести, но и как-то применить. Поэтому они копятся, забываются, а потом просто становится не до них. Как, к примеру, выглядит работа с сайтом? Есть у вас на компьютере локальная версия, в ней вы и «играетесь», а потом публикуете. Физически нужен доступ к этой версии, компьютер. Короче, это неудобно, поэтому давайте попробуем другой способ. Пусть код сайта хранится где-то в облаке, а изменения заливаются на хостинг автоматически. Быстро, без ноутбука, без каких-то программ для загрузки файлов, без «доеду до дома — поправлю».

Называется это автоматической публикацией — вы отправляете изменения в специальное хранилище кода, а дальше сервис сам берёт свежую версию сайта и выкладывает её на хостинг в соответствии с правилами, которые вы определили. Я хочу показать, как это устроено, на примере реального проекта на PHP. А в следующих статьях разберём, как быть с сервисом на Node.js и как автоматически собирать нативные мобильные приложения без компьютера и посторонних сервисов. Начнём с сайта, потому что это просто.

Как обычно выкладывают сайт

Классический способ — зайти на хостинг по FTP или SFTP каким-нибудь файловым клиентом (например, FileZilla), найти на компьютере изменённые файлы и перетащить их на сервер поверх старых.

Работает это до поры до времени, а потом начинаются мелкие неприятности. Забыли залить один из изменённых файлов — и часть сайта работает по-старому. Не помните, какая версия файла сейчас на сервере — та, что вы правили вчера, или ещё более старая. Два человека работают над одним проектом — и рискуют затереть правки друг друга, просто перезалив файлы поверх. А если что-то сломалось после заливки — вернуть как было можно только если у вас случайно осталась резервная копия старых файлов.

Ни истории изменений, ни возможности откатиться на шаг назад, ни ясности, что именно сейчас лежит на сервере.

Репозиторий — это надёжнее

Есть другой подход: хранить код сайта не просто как папку с файлами на компьютере, а в системе контроля версий — например, в git. Такое хранилище называется репозиторием, а сервисов, которые их у себя размещают, много — самый известный GitHub.

Если совсем просто: репозиторий запоминает каждое изменение файлов как отдельный шаг с датой, автором и описанием. Захотели узнать, что изменилось за последний месяц, — посмотрели историю. Что-то сломали — вернулись на шаг назад одной командой. Работаете вдвоём — правки не затираются, а аккуратно складываются друг на друга.

Но главное для нашей темы — другое. Раз весь сайт целиком лежит в репозитории, то у вас появляется однозначный ответ на вопрос «а что сейчас на сайте?»: то, что лежит в репозитории в основной ветке (обычно она называется main), — то и должно быть на сайте. И никакой путаницы.

Можно работать откуда угодно

И вот тут открывается неочевидная выгода. Раз ваш сайт — это просто репозиторий на GitHub, работать с ним можно не только с рабочего компьютера, где стоит привычная программа для редактирования кода.

А ещё с репозиториями хорошо работают помощники вроде Claude Code — им можно описать задачу («поправь вёрстку карточки на мобильных» или «добавь новую страницу с прайсом»), и они сами напишут код и отправят изменение в репозиторий. То есть можно между делом, буквально с телефона, попросить что-то поправить или попробовать новую идею на сайте — и не нужно вообще садиться за компьютер.

Что такое GitHub Actions

GitHub Actions — это встроенный в GitHub робот, который умеет что-то делать автоматически при разных событиях в репозитории. Самое частое событие — отправка изменений (это и называется словом push, «отправить»): вы отправили правки в основную ветку — робот заметил это и запустил заранее описанный набор шагов.

Набор шагов описывается в обычном текстовом файле в формате YAML, который лежит прямо в репозитории по пути .github/workflows/имя-файла.yml. В этом файле написано человеческим языком (ну, почти): «при отправке изменений в ветку main — скачай свежий код и отправь его на хостинг по такому-то адресу».

Готовим SSH-ключ

Чтобы робот GitHub Actions мог зайти на хостинг и оставить там файлы, ему нужен способ авторизоваться — представиться и доказать, что он действительно имеет право туда заходить. Самый надёжный вариант — SSH-ключ.

SSH-ключ — это пара файлов: закрытый (приватный) и открытый (публичный). Работает это так: открытый ключ кладётся на сервер и говорит «пускай того, у кого есть парный закрытый ключ». Закрытый ключ никому не показывается и остаётся только у вас — точнее, в нашем случае, только у GitHub, в специальном защищённом хранилище, о котором дальше.

Создать пару ключей можно одной командой в терминале (на Mac и Linux — обычный терминал, на Windows — например, PowerShell или Git Bash):

ssh-keygen -t ed25519 -C "deploy-key"

На вопросы, где сохранить файл и нужен ли пароль на сам ключ, можно просто нажимать Enter, соглашаясь со значениями по умолчанию (для ключа, который будет использовать робот, а не вы лично, пароль на сам ключ не нужен — иначе его некому будет вводить). В результате появятся два файла: например, id_ed25519 (закрытый ключ) и id_ed25519.pub (открытый).

Кладём ключ на хостинг и в настройки репозитория

Дальше открытый ключ (файл с окончанием .pub) нужно положить на хостинг, а закрытый — спрятать в настройках репозитория на GitHub, откуда его сможет достать только сам GitHub Actions.

Я показываю на примере хостинга NetAngels — пользуюсь им давно, но подойдёт в целом любой хостинг с поддержкой SSH, шаги будут примерно такими же.

В личном кабинете NetAngels заходим в нужный контейнер сайта и открываем вкладку «SSH». Там же будут видны логин (что-то вроде c123456), адрес сервера (h60.netangels.ru) и порт (обычно 22) — эти данные понадобятся чуть позже.

Блок SSH-доступ к контейнеру в личном кабинете NetAngels: логин, IP-адрес, адрес сервера и список SSH-ключей

Нажимаем «Добавить ключ», в выпадающем списке выбираем «Создать новый ключ», указываем любое понятное название (например, «GitHub Actions») и вставляем в поле «Ключ» содержимое файла id_ed25519.pub — того самого, открытого. Нажимаем «Добавить ключ».

Форма добавления SSH-ключа в NetAngels с полями «Имя ключа» и «Ключ»

Теперь идём в репозиторий на GitHub, в раздел Settings → Secrets and variables → Actions. Там два похожих, но разных списка:

Добавляем секреты (кнопка «New repository secret»):

В итоге список секретов должен выглядеть примерно так:

Список Repository secrets в настройках GitHub: SSH_HOST, SSH_KEY, SSH_PORT, SSH_USERNAME

И одну переменную (кнопка «New repository variable», на отдельной вкладке «Variables» рядом с «Secrets»):

Вкладка Variables в настройках GitHub с переменной DESTINATION_PATH

Сам файл: разбираем построчно

Ниже файл .github/workflows/deploy.yml, которым я пользуюсь у себя в одном из PHP-проектов:

name: Deploy to NetAngels

on:
  push:
    branches: [ main, master ]
  workflow_dispatch:

jobs:
  deploy:
    name: Deploy to NetAngels hosting
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v7

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.5'

    - name: Validate PHP syntax
      run: |
        echo "🔍 Checking PHP syntax..."
        find www -name "*.php" -exec php -l {} \; | tee php_check.log
        if grep -q "Parse error\|Fatal error\|Syntax error" php_check.log; then
          echo "❌ PHP syntax errors found!"
          exit 1
        fi
        echo "✅ PHP syntax validation passed"
        rm php_check.log

    - name: 🚀 Deploy via rsync
      uses: burnett01/rsync-deployments@v9
      with:
        switches: -avzr --delete --exclude-from=.rsyncignore
        path: ./www/
        remote_path: $
        remote_host: $
        remote_user: $
        remote_key: $
        remote_port: $

    - name: Verify deployment
      run: |
        echo "🔍 Verifying deployment..."
        echo "✅ Deployment completed successfully!"
        echo "📊 Deployment summary:"
        echo "   - Server: $"
        echo "   - Directory: $"
        echo "   - Branch: $"
        echo "   - Commit: $"
        echo "   - Time: $(date)"

    - name: Notify on failure
      if: failure()
      run: |
        echo "❌ Deployment failed!"
        echo "Please check the logs above for details."
        echo "Common issues:"
        echo "  - SSH key not added to server"
        echo "  - SSH username incorrect"
        echo "  - Server directory doesn't exist"
        echo "  - Permission issues"
        echo "  - PHP syntax errors"

Разберём по частям:

Ключ $ — это то место, где GitHub подставляет ваш закрытый ключ в момент выполнения, никому его не показывая: ни в журнале выполнения, ни в самом файле настроек, который лежит в открытом репозитории.

Флаги -avzr --delete у rsync означают: скопировать файлы рекурсивно, со сжатием, сохраняя даты изменения, и удалить на сервере то, чего больше нет в репозитории — чтобы сервер всегда точно повторял содержимое ветки, а не постепенно обрастал забытым старьём.

Раз уж мы здесь: обратите внимание на цифры версий у сторонних actions в файле — actions/checkout@v7, burnett01/rsync-deployments@v9. Их стоит время от времени проверять на актуальность: выходят более новые версии с исправлениями, а совсем старые рано или поздно перестают нормально работать. Сделать это просто: берём часть до @ (например, burnett01/rsync-deployments) и открываем в браузере github.com/burnett01/rsync-deployments — это и есть страница репозитория того action’а на GitHub, а на ней видно, какая версия сейчас самая свежая.

Что кладём в .rsyncignore

Рядом с deploy.yml в репозитории лежит ещё один файл — .rsyncignore. Это простой список того, что не нужно отправлять на сервер, даже если оно случайно оказалось в папке www:

.git
.github
.gitignore
.gitattributes
.vscode
.idea
.editorconfig
.envrc
.env*
vendor
*.log
logs
tmp
*.tmp
*.bak
*cookies*.txt
config.php
debug.php
phpinfo.php
test.php
bin
README.md
.DS_Store
Thumbs.db

Смысл в основном такой:

Здесь есть важный нюанс: --exclude-from работает вместе с --delete по правилу «не трогать». Если такой файл случайно уже лежит на сервере — --delete его не удалит просто потому, что он подходит под исключение. Список работает только в одну сторону: не давать таким файлам попасть на сервер через эту отправку, а не выковыривать их оттуда, если они там уже были.

Пробуем

Сохраняем файл deploy.yml, отправляем это изменение в репозиторий — и дальше можно зайти на GitHub, во вкладку Actions, и увидеть, как запустился и выполняется наш сценарий: шаг за шагом, с журналом всего происходящего. Через несколько секунд напротив всех шагов появятся зелёные галочки, а обновлённый сайт уже будет на хостинге.

Завершённый запуск workflow Deploy to NetAngels hosting в GitHub Actions: все шаги отмечены зелёными галочками Шаг «Notify on failure» остался серым — он выполняется только при ошибке

Дальше это работает само: любое изменение, отправленное в main — хоть с ноутбука, хоть из приложения GitHub на телефоне, хоть руками того же Claude Code, — само доедет до сайта за считаные секунды, без каких-либо действий с вашей стороны.

Что дальше

Мы разобрались, как сайт может публиковаться сам, при каждой отправке изменений в репозиторий. В следующих статьях посмотрим на вещи чуть сложнее: как таким же способом работать с проектом на Node.js, автоматически собрать мобильное приложение под Android или iOS и отправить его на тестирование — тоже без единого лишнего действия с компьютера.