Перейти к содержимому

Git hook post-receive для деплоя на сервере

Деплой через CI — это хорошо, но иногда нужно просто закинуть код на сервер одним git push. post-receive hook в bare-репозитории решает эту задачу без лишних зависимостей: пушитшь на сервер — хук автоматически вытягивает файлы в рабочую директорию.

Схема: bare repo как деплой-триггер

Логика простая:

  1. На сервере создаётся bare-репозиторий (например, /srv/deploy/app.git).
  2. Разработчик добавляет его как remote и делает git push origin main.
  3. Git принимает данные и запускает hooks/post-receive.
  4. Скрипт хука делает git --work-tree=/var/www/app --git-dir=/srv/deploy/app.git checkout -f main.
Примечание

Bare-репозиторий не содержит рабочей директории. Именно поэтому в post-receive нужно явно указывать --work-tree, чтобы checkout знал, куда записать файлы.

Это не CI — это прямой триггер на уровне Git. Никаких пайплайнов, артефактов, очередей. Подходит для небольших сервисов, VPS и внутренних инструментов.

Создание bare репозитория на сервере

Подключаешься к серверу и создаёшь repo:

mkdir -p /srv/deploy
cd /srv/deploy
git init --bare app.git

После этого структура будет стандартной: hooks/, objects/, refs/, HEAD, config. Хуки по умолчанию лежат в /srv/deploy/app.git/hooks/ с суффиксом .sample — их нужно переименовать или создать свои.

Предупреждение

Убедись, что директория /srv/deploy принадлежит пользователю, от которого ты пушишь. Иначе права на запись в objects/ будут закрыты.

Написание hook post-receive

Создаёшь файл /srv/deploy/app.git/hooks/post-receive:

#!/usr/bin/env bash
set -euo pipefail

REPO_DIR="/srv/deploy/app.git"
WORK_TREE="/var/www/app"
BRANCH="main"

while read oldrev newrev refname; do
  if [ "$refname" = "refs/heads/$BRANCH" ]; then
    echo "Deploying $BRANCH to $WORK_TREE..."
    git --work-tree="$WORK_TREE" --git-dir="$REPO_DIR" checkout -f "$BRANCH"
    echo "Deployment complete."
  fi
done

Ключевые моменты:

ЭлементНазначение
set -euo pipefailСкрипт падает при любой ошибке, не продолжает с неверными данными
while read oldrev newrev refnameХук передаёт три аргумента построчно за каждый обновлённый ref
refs/heads/$BRANCHПроверяем, что пушится именно нужная ветка
checkout -fПринудительно синхронизирует рабочее дерево с референсом
Подсказка

Если после деплоя нужно перезапустить сервис, добавь в конец скрипта systemctl restart app или supervisorctl reload app. Хук выполняется в контексте Git-пользователя, поэтому убедись, что у него есть права на перезапуск.

Настройка прав и доступа

Самая частая проблема — права. Git-пользователь, в который ты пушишь, должен иметь право писать в WORK_TREE и выполнять команды из хука.

Варианты настройки:

  • Один пользователь: Git-юзер и веб-пользователь — одно лицо. Просто chown -R deploy:deploy /srv/deploy /var/www/app.
  • Группа: Добавляешь Git-пользователя в группу веб-сервера. usermod -aG www-data deploy, затем chmod -R g+w /var/www/app.
  • SSH-ключ: Пушишь через SSH, ключ авторизуется под нужным пользователем.
# Пример: деплой-пользователь deploy, веб-корень /var/www/app
sudo useradd -m deploy
sudo mkdir -p /var/www/app
sudo chown -R deploy:deploy /var/www/app
sudo chmod -R 755 /var/www/app

Не забудь сделать хук исполняемым:

chmod +x /srv/deploy/app.git/hooks/post-receive

Пуш с клиента и проверка

На машине разработчика добавляешь remote и пушишь:

git remote add production deploy@server:/srv/deploy/app.git
git push production main

На сервере в логе хука увидишь:

Deploying main to /var/www/app...
Deployment complete.

Проверяешь файлы на сервере:

ls -la /var/www/app
git --git-dir=/srv/deploy/app.git --work-tree=/var/www/app status
Предупреждение

Если пуш падает с Permission denied — проверьй владельца hooks/post-receive и целевой директории. Если checkout не записывает файлы — убедись, что WORK_TREE указывает на существующую директорию и у пользователя есть на неё права.

Это всё. Никаких дополнительных инструментов — только Git и shell. Для продакшена с нагрузкой это, разумеется, не замена нормальному CI, но для быстрого деплоя на один-два сервера работает надёжно и без лишней магии.