Любой проект — это много мелких правок, часть которых можно делать «между делом». Но мы часто их откладываем, ведь в классическом варианте эти правки нужно не только внести, но и как-то применить. Поэтому они копятся, забываются, а потом просто становится не до них. Как, к примеру, выглядит работа с сайтом? Есть у вас на компьютере локальная версия, в ней вы и «играетесь», а потом публикуете. Физически нужен доступ к этой версии, компьютер. Короче, это неудобно, поэтому давайте попробуем другой способ. Пусть код сайта хранится где-то в облаке, а изменения заливаются на хостинг автоматически. Быстро, без ноутбука, без каких-то программ для загрузки файлов, без «доеду до дома — поправлю».
Называется это автоматической публикацией — вы отправляете изменения в специальное хранилище кода, а дальше сервис сам берёт свежую версию сайта и выкладывает её на хостинг в соответствии с правилами, которые вы определили. Я хочу показать, как это устроено, на примере реального проекта на 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) — эти данные понадобятся чуть позже.
Нажимаем «Добавить ключ», в выпадающем списке выбираем «Создать новый ключ», указываем любое понятное название (например, «GitHub Actions») и вставляем в поле «Ключ» содержимое файла id_ed25519.pub — того самого, открытого. Нажимаем «Добавить ключ».
Теперь идём в репозиторий на GitHub, в раздел Settings → Secrets and variables → Actions. Там два похожих, но разных списка:
- Secrets — для по-настоящему секретных данных, вроде паролей и ключей. Значение можно один раз задать, но потом нигде, даже вам самим, посмотреть его снова не получится — только заменить.
- Variables — для данных, которые не секрет, а просто настройка, например путь к папке на сервере.
Добавляем секреты (кнопка «New repository secret»):
SSH_HOST— адрес сервера (h60.netangels.ru)SSH_USERNAME— логин с той же вкладки «SSH»SSH_PORT— порт (обычно22)SSH_KEY— содержимое закрытого ключа целиком, файлаid_ed25519(не.pub!)
В итоге список секретов должен выглядеть примерно так:
И одну переменную (кнопка «New repository variable», на отдельной вкладке «Variables» рядом с «Secrets»):
DESTINATION_PATH— путь до папки на сервере, куда нужно класть файлы сайта (включая конечную папку www, если она есть), например/home/c123456/my-cool-website.ru/www
Сам файл: разбираем построчно
Ниже файл .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"
Разберём по частям:
on: push: branches: [ main, master ]плюсworkflow_dispatch:— запускать сценарий при отправке изменений сразу в обе ветки (у старых проектов основная ветка иногда называетсяmaster, а неmain), аworkflow_dispatchдобавляет в интерфейсе GitHub кнопку «Run workflow» — можно запустить публикацию вручную, без нового изменения, если, например, просто хочется выложить то же самое ещё раз.runs-on: ubuntu-latest— GitHub на минуту-другую выделяет под задачу временный компьютер с Linux. Он появляется, выполняет шаги и после этого исчезает — никаких следов не остаётся, кроме журнала выполнения.- Шаг
Checkout code— скачивает на этот временный компьютер код репозитория. - Шаг
Setup PHP— на временном компьютере по умолчанию нет PHP, поэтому его ставит отдельным шагом, той же версии, что и на хостинге (8.5), чтобы дальнейшая проверка была честной. - Шаг
Validate PHP syntax— прогоняет каждый.php-файл в папкеwwwчерез встроенную проверку синтаксиса PHP (php -l, l — от «lint»), собирает результат в файл-журнал и ищет там слова вроде «Parse error». Если находит — сценарий останавливается с ошибкой (exit 1) и до следующего шага, самой отправки на сервер, дело просто не доходит. Опечатка в коде так и остаётся опечаткой в репозитории, а не превращается в сломанный сайт. Эмодзи вecho— просто украшение для журнала выполнения, чтобы результат было проще пробежать глазами. - Шаг
🚀 Deploy via rsync— тот самыйburnett01/rsync-deployments, который копирует файлы на сервер по SSH программойrsync.path: ./www/— на сервер уходит содержимое только папкиwww, где лежит собственно сайт, а не весь репозиторий целиком (там ведь есть ещё и сам файлdeploy.yml, и служебные файлы, которым на сервере делать нечего).--exclude-from=.rsyncignoreподключает список исключений — отдельный файл, о котором дальше. - Шаги
Verify deploymentиNotify on failureне делают ничего технического — это просто аккуратный вывод в журнал: что именно и когда было выложено, а если что-то из предыдущих шагов сломалось (if: failure()) — подсказка, в какую сторону смотреть. Настоящего отката к прошлой версии тут нет, но и без него, если ошибка — на этапе проверки синтаксиса сайт просто не тронут, а не сломан наполовину.
Ключ $ — это то место, где 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
Смысл в основном такой:
- Служебные папки редактора и git (
.git,.github,.vscode,.idea,.editorconfig) — нужны только вам на компьютере, для работы сайта абсолютно бесполезны. .envrcи.env*— файлы с настройками окружения, которые часто как раз содержат пароли и ключи от разных сервисов. Такому точно не место в публичной выгрузке.vendor— папка со сторонними библиотеками, которые PHP подключает к проекту (аналогnode_modulesв мире JavaScript). Она может быть тяжёлой и либо уже стоит отдельно на сервере, либо ставится там своим шагом.*.log,logs,tmp,*.tmp,*.bak,*cookies*.txt— временные и отладочные файлы, которые появляются в процессе работы и разработки, но не должны быть частью самого сайта.config.php,debug.php,phpinfo.php,test.php— файлы, которые легко забыть на компьютере в «отладочном» виде: с настоящими паролями от базы данных или с командойphpinfo(), которая выводит подробности о сервере всем желающим. Лучше исключить их из автоматической выгрузки навсегда, чем один раз забыть и пожалеть.README.md,bin,.DS_Store,Thumbs.db— описание проекта для разработчиков и системный мусор, который оставляют macOS и Windows, — тоже никак не связаны с самим сайтом.
Здесь есть важный нюанс: --exclude-from работает вместе с --delete по правилу «не трогать». Если такой файл случайно уже лежит на сервере — --delete его не удалит просто потому, что он подходит под исключение. Список работает только в одну сторону: не давать таким файлам попасть на сервер через эту отправку, а не выковыривать их оттуда, если они там уже были.
Пробуем
Сохраняем файл deploy.yml, отправляем это изменение в репозиторий — и дальше можно зайти на GitHub, во вкладку Actions, и увидеть, как запустился и выполняется наш сценарий: шаг за шагом, с журналом всего происходящего. Через несколько секунд напротив всех шагов появятся зелёные галочки, а обновлённый сайт уже будет на хостинге.
Шаг «Notify on failure» остался серым — он выполняется только при ошибке
Дальше это работает само: любое изменение, отправленное в main — хоть с ноутбука, хоть из приложения GitHub на телефоне, хоть руками того же Claude Code, — само доедет до сайта за считаные секунды, без каких-либо действий с вашей стороны.
Что дальше
Мы разобрались, как сайт может публиковаться сам, при каждой отправке изменений в репозиторий. В следующих статьях посмотрим на вещи чуть сложнее: как таким же способом работать с проектом на Node.js, автоматически собрать мобильное приложение под Android или iOS и отправить его на тестирование — тоже без единого лишнего действия с компьютера.