Каждый разработчик рано или поздно сталкивается с необходимостью обновлять приложение на сервере. Под деплоем понимают процесс переноса приложения из стадии разработки в рабочее окружение. Делать это вручную не хочется, поэтому автоматизация деплоя помогает сэкономить время и исключить ошибки.
В статье рассказали, как организовать деплой проекта на белорусском VPS с помощью Git и systemd.
Подготовка сервера
Сначала подготовьте сам сервер, арендуйте VPS и настройте операционную систему. Для деплоя хорошо подходят дистрибутивы вроде Ubuntu или Debian, но можно взять CentOS или другой Linux. После доступа по SSH первым делом обновите систему и установите базовые инструменты. Введите в консоли:
sudo apt update && sudo apt install git nginx docker.io -y
Это установит Git для клонирования репозитория, Nginx для веб-сервера и отдачи статики и Docker, если нужны контейнеры. Если ваш проект написан на Python, установите необходимые пакеты:
sudo apt install python3-venv python3-pip
Если приложение на Node.js, поставьте Node и npm:
sudo apt install nodejs npm
Также может понадобиться СУБД, если вы используете базы данных:
sudo apt install postgresql
Проверьте, есть ли доступ по SSH. Сгенерируйте ключ ssh-keygen и добавьте публичный ключ в /home/deploy/.ssh/authorized_keys, чтобы вы могли пушить код без пароля. Создайте пользователя для деплоя командой sudo adduser deploy и назначьте ему нужные права. На всякий случай настройте файрвол, разрешите порты 22, 80 и 443 через ufw. Все эти базовые действия — обычная подготовка, и они экономят кучу времени в будущем.
Настройка Git-репозитория на сервере
Теперь займёмся репозиторием проекта. Есть два распространённых подхода: bare-репозиторий и pull-подход. В варианте bare-репо вы создаёте голый репозиторий на сервере и пушите туда. Выполните на сервере:
ssh deploy@server
git init --bare /home/deploy/project.git
В такой папке хранится только структура Git без рабочих файлов. Bare-репозиторий — это своего рода чистый контейнер. В нём хранятся только метаданные Git, без самих файлов проекта. Вы можете не опасаться перезаписать рабочую копию. Содержимое вашего приложения будет появляться в отдельной папке /home/deploy/app после выполнения хука. Потом в локальном проекте добавьте новый remote:
git remote add vps ssh://deploy@server/home/deploy/project.git
git push vps main
Так вы отправите изменения на VPS.
Чтобы код из bare-репо оказался в нужном месте, пишем хук. В каталоге /home/deploy/project.git/hooks создайте файл post-receive и сделайте его исполняемым (chmod +x post-receive):
#!/bin/bash
GIT_WORK_TREE=/home/deploy/app git checkout -f
systemctl restart myapp.service
Эта простая последовательность сначала распаковывает из репозитория актуальные файлы в папку /home/deploy/app, а затем перезапускает сервис приложения. Если репозиторий содержит несколько веток, можно деплоить только конкретную, например, main. Для этого в post-receive проверяют переменную ref. Например:
while read oldrev newrev ref; do
if [[ "$ref" == "refs/heads/main" ]]; then
GIT_WORK_TREE=/home/deploy/app git checkout -f main
sudo systemctl restart myapp.service
fi
done
В этом случае пуши в другие ветки будут игнорироваться, а на main деплой произойдёт автоматически. Если вам не нравится bare-репо, можно сразу клонировать проект в папку и обновлять командой git pull. Однако bare-подход часто удобнее, потому что всегда чистый и независимый. Схема работы почти не отличается от CI/CD-процессов: как говорится в примере, скрипт деплоя копирует проект на сервер по SSH и перезапускает systemd-сервис. После git push vps main ваш код оказывается на сервере без дополнительного вмешательства.
Создание и настройка systemd-сервиса
Далее нужно создать unit-файл для вашего приложения. Заведите файл /etc/systemd/system/myapp.service со следующим содержимым:
[Unit]
Description=MyApp Service
After=network.target
[Service]
User=deploy
WorkingDirectory=/home/deploy/app
ExecStart=/usr/bin/python3 /home/deploy/app/app.py
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Здесь User и WorkingDirectory указывают, под каким пользователем и в какой папке запускать приложение. ExecStart — это команда запуска для Python-файла или исполняемого скрипта. Если вы используете виртуальное окружение, путь до интерпретатора нужно указать явно, например, ExecStart=/home/deploy/app/venv/bin/python3 /home/deploy/app/app.py, чтобы systemd запустил именно ваш виртуальный Python. Опция Restart=on-failure говорит systemd автоматически перезапускать сервис при сбое. RestartSec=5s добавляет паузу в 5 секунд перед перезапуском, чтобы не гонять ресурсами.
По умолчанию systemd отправляет логи в свою подсистему journald. Если нужно выводить их в syslog, чтобы, например, собирать логи централизованно, можно добавить строки в секцию:
[Service]: StandardOutput=syslog SyslogIdentifier=myapp
Это позволит отличать логи вашего приложения в общем журнале. После создания или изменения unit-файла выполните:
sudo systemctl daemon-reload
sudo systemctl start myapp
sudo systemctl enable myapp
Команда daemon-reload сообщит systemd о новом/изменённом сервисе. После start сервис запустится сразу, а enable включит его при старте сервера. Проверить статус приложения можно командой systemctl status myapp, а полные логи смотреть через journalctl -u myapp.
systemd будет держать ваш сервис в фоне, перезапускать при сбоях и запускать при загрузке сервера. Можно прописать запуск бекап-скриптов или других вспомогательных процессов в этом же unit-файле. Например, в секции [Service] можно указать Environment=MY_ENV=prod или другие переменные окружения прямо здесь, чтобы не хранить секреты в коде. Главное убедиться, что файлы в /home/deploy/app принадлежат пользователю deploy, иначе скрипт не сможет их читать.
Интеграция Git-хука и сервиса
Остаётся связать всё воедино. Когда хук выполняет команду checkout или pull, нужно, чтобы сервис увидел обновлённый код. Достаточно вызвать systemctl restart. Например, полный скрипт /home/deploy/project.git/hooks/post-receive может выглядеть так:
#!/bin/bash
echo "Получаем последние изменения..."
GIT_WORK_TREE=/home/deploy/app git checkout -f
echo "Останавливаем и перезапускаем сервис..."
sudo systemctl restart myapp.service
Скрипт выводит сообщения в журнал, обновляет файлы из репозитория и перезапускает сервис. Можно добавить небольшую паузу (sleep 2) между командами, чтобы убедиться, что предыдущий процесс завершился. Если приложение требует сборки или установки зависимостей, добавьте это в хук. К примеру:
cd /home/deploy/app
npm ci
npm run build
Или для Python/Django:
python manage.py migrate
Главное, чтобы команды выполнялись от пользователя deploy, поэтому если он не имеет прав на перезапуск сервиса, можно дать ему нужные привилегии через sudo. Например, добавить в /etc/sudoers строку:
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp.service
И вызывать sudo systemctl restart в скрипте.
В некоторых случаях вы хотите обновить только конфигурацию веб-сервера. Например, если изменился файл Nginx или статический контент, можно вместо полного перезапуска службы приложения выполнить:
sudo systemctl reload nginx
Это применит новую конфигурацию без остановки сервера. Наконец, после того как хук завершил свою работу, сервер увидит изменения и запустит обновлённый код. Эта схема превращает деплой в одну команду. Как отмечено в примере, достаточно запустить git pull, и дальше systemd сам поднимет сервис. Привычное «влепить файлы вручную» уходит в прошлое.
Тестирование и практические советы
Когда всё настроено, протестируйте процесс. Сделайте пробный коммит, например, поменяйте строку комментария в коде, и выполните git push vps main. После этого загляните в логи:
sudo journalctl -u myapp -f
Здесь отображаются сообщения при старте и возможные ошибки. Если приложение не запустилось, команда systemctl status myapp подскажет причину. После успешного обновления вы можете буквально забыть про деплой: сделали коммит вечером за чашкой чая, а к утру новое изменение уже развёрнуто в продакшене. Такой подход превращает деплой в невидимую операцию, позволяя сосредоточиться на любимых задачах, пока сервер делает всю рутинную работу.
Не забудьте о безопасности. На этапе тестирования удостоверьтесь, что приложение корректно запускается от указанного пользователя и папки. Настройте файрвол или fail2ban для SSH-подключений, чтобы отпугнуть злоумышленников. И главное — делайте резервные копии баз данных и важных файлов перед крупным обновлением. К примеру, можно настроить скрипт бекапа базы данных:
pg_dump mydatabase > /home/deploy/db_backup_$(date +%F).sql
И запускать его перед деплоем. Тогда при критической ошибке вы легко вернётесь к предыдущему состоянию.
Если в будущем захотите развернуть на том же сервере ещё один проект, просто заведите для него новый Git-репозиторий и unit-файл. В итоге каждый ваш сервис будет самоустанавливающимся, осталось только нажать git push, как по волшебству. Стоит также настроить уведомления или дополнительные логи, например, добавьте в скрипт запись в общий лог-файл или отправку сообщения (Telegram/Slack) о новом деплое, чтобы команда точно видела результат.
Заключение
Автоматизация деплоя освобождает ваше время и позволяет сконцентрироваться на самом важном — развитии проекта. Теперь достаточно сделать push в репозиторий, и сервер сам подтянет обновления и перезапустит сервис. Пусть рутинные задачи возьмёт на себя система, вы же занимайтесь новыми идеями и фичами. Такой подход не только экономит нервы, но и даёт ощущение контроля и порядка в продакшене.
За стабильность — с бонусом
Долгосрочные клиенты получают больше: +1 месяц в подарок по промокоду