Skip to main content
← Назад к блогу
VercelGit HooksCI/CDDeploymentpnpm

Деплой на Vercel из git pre-push хука: репозиторий организации, план Hobby, без секретов CI

Git-интеграция Vercel отказывается подключать приватный репозиторий GitHub-организации на плане Hobby, а путь через CI требует секретов репозитория, которые тебе могут не разрешить добавить. Вот вариант полегче: локальный pre-push хук, который деплоит через Vercel CLI ровно тот коммит, что был запушен, — а все по-прежнему пушат под своим именем.

Опубликовано 6 октября 2026 г.7 мин чтения
Дополнение к посту Деплой на Vercel из CI, когда автор коммита не член команды. Тот пост деплоит из GitHub Actions и лучше подходит, когда ты можешь добавлять секреты в репозиторий. Этот — на случай, когда не можешь, и он вообще не требует переписывать коммиты.

Репозиторий переехал в GitHub-организацию, проект Vercel живёт в команде на плане Hobby, а все участники пушат под своими GitHub-именами. Я хотел, чтобы каждый пуш деплоился — main в прод, любая другая ветка в превью, — не меняя, кому принадлежат коммиты.

Первым планом была нативная Git-интеграция. Она провалилась три раза подряд, каждый раз по другой причине:

Error: Failed to link your-org/your-repo. You need to add a Login Connection
to your GitHub account first. (400)

This action must be performed by an organization owner

Error: The repository "your-repo" is private and owned by an organization,
which is not supported on the Hobby plan. Upgrade to Pro to continue. (409)

Три стены между тобой и Git-интеграцией

  1. Не подключён вход через GitHub. Аккаунт Vercel, которому принадлежит команда, ни разу не подключал GitHub, поэтому привязка репозитория падает с первой ошибкой выше. Решение — Account Settings → Authentication, но подумай, прежде чем нажимать: аккаунт GitHub можно подключить только к одному аккаунту Vercel одновременно. Подключив свой к сервисному аккаунту, ты отвяжешь его от своего личного Vercel, и Vercel начнёт блокировать деплои твоих личных проектов («Git author must have access to the project»). Хуку ниже подключение GitHub вообще не нужно, так что делай это, только если идёшь именно за Git-интеграцией.
  2. Для установки приложения нужен владелец организации. GitHub App от Vercel нужно установить на уровне организации. Обычный участник может только запросить установку, а страница настроек отвечает «This action must be performed by an organization owner».
  3. Hobby не умеет подключать приватные репозитории организаций. Даже когда всё выше сделано, Vercel всё равно отвечает 409. Никакая настройка этого не исправит: ограничение — сам план.

Первые две — это разрешения, которые можно выбить. Третья значит, что Git-интеграция отпадает, пока команда не перейдёт на Pro. Всё, что дальше, — обходной путь, так что выбирай тот, который честно признаёт, что он обходной.

Деплоить с машины, которая пушит

Vercel CLI авторизуется логином или токеном, а не автором коммита, поэтому никогда не запускает проверку членства, которая блокирует интеграцию. Мой прошлый пост запускает CLI в GitHub Actions. Для этого нужен VERCEL_TOKEN в секретах репозитория, то есть права админа на репозиторий, — плюс --amend автора в раннере, чтобы дашборд показывал коммит.

Хук pre-push обходится без того и другого. Он запускается на машине разработчика после git push, деплоит только что запушенный коммит и никогда не трогает сам коммит: настоящий автор остаётся и на GitHub, и в дашборде. Цена в том, что он живёт на каждой машине отдельно — об этом ниже.

Сам хук

Сохрани его как .git/hooks/pre-push и сделай исполняемым. Git вызывает его с именем и URL удалённого репозитория и подаёт на stdin по одной строке на каждый пушимый ref.

.git/hooks/pre-push
#!/bin/bash
# Deploy to Vercel after a successful push. Local only: git never pushes .git/hooks.
# main -> production, any other branch -> preview. Runs detached; log: .git/vercel-deploy.log

case "$2" in
  *your-org/your-repo*) ;;
  *) exit 0 ;;
esac

repo_root="$(git rev-parse --show-toplevel)"
gh_path="${2#*github.com[:/]}"; gh_path="${gh_path%.git}"
gh_org="${gh_path%%/*}"; gh_repo="${gh_path#*/}"
log="$repo_root/.git/vercel-deploy.log"
push_pid=$PPID

[ -d "$repo_root/.vercel" ] || { echo "vercel-deploy: run 'vercel link' first, skipping deploy" >&2; exit 0; }

while read -r _ local_sha remote_ref _; do
  [ "$local_sha" = "0000000000000000000000000000000000000000" ] && continue
  branch="${remote_ref#refs/heads/}"
  if [ "$branch" = "main" ]; then flag="--prod"; else flag=""; fi

  (
    # wait for the push itself to finish, then deploy exactly the pushed commit
    while kill -0 "$push_pid" 2>/dev/null; do sleep 1; done
    # a rejected or failed push must not deploy: only continue if the commit reached the remote
    landed="$(git ls-remote "$2" "$remote_ref" 2>/dev/null | cut -f1)"
    if [ "$landed" != "$local_sha" ]; then
      echo "=== $(date '+%F %T') $branch @ ${local_sha:0:7} push did not land, deploy skipped" >> "$log"
      exit 0
    fi
    tmp="$(mktemp -d)"
    cd "$repo_root" || exit 1
    git worktree add --detach "$tmp" "$local_sha" >/dev/null 2>&1 || exit 1
    cp -R "$repo_root/.vercel" "$tmp/.vercel"
    cd "$tmp" || exit 1
    {
      echo "=== $(date '+%F %T') $branch @ ${local_sha:0:7} ${flag:-preview}"
      vercel deploy $flag --yes \
        --build-env ENABLE_EXPERIMENTAL_COREPACK=1 \
        -m "githubDeployment=1" \
        -m "githubOrg=$gh_org" -m "githubRepo=$gh_repo" \
        -m "githubCommitOrg=$gh_org" -m "githubCommitRepo=$gh_repo" \
        -m "githubCommitMessage=$(git -C "$repo_root" log -1 --format=%s "$local_sha")" \
        -m "githubCommitSha=$local_sha" \
        -m "githubCommitRef=$branch" \
        -m "githubCommitAuthorName=$(git -C "$repo_root" log -1 --format=%an "$local_sha")" \
        -m "githubCommitAuthorEmail=$(git -C "$repo_root" log -1 --format=%ae "$local_sha")" \
        2>&1 | tail -5
    } >> "$log"
    cd "$repo_root" && git worktree remove --force "$tmp"
  ) >/dev/null 2>&1 &
  disown
done

exit 0

Всё держится на четырёх деталях:

  • Сверяй URL удалённого репозитория, а не его имя. Git передаёт URL как $2. При пуше в любой другой удалённый репозиторий хук сразу выходит, поэтому зеркало или форк никогда не задеплоятся.
  • Дождись пуша, потом проверь, что он дошёл. Хук запускается до того, как пуш завершится, и при отклонённом пуше он тоже запускается. Поэтому он уходит в фон, ждёт, пока процесс git push завершится, и деплоит, только если git ls-remote показывает твой SHA на удалённом репозитории.
  • Деплой запушенный коммит, а не рабочее дерево. Одноразовый git worktree на запушенном SHA гарантирует, что незакоммиченные правки и недописанные файлы никогда не попадут в деплой. Он переиспользует твою привязку .vercel.
  • main — это прод, всё остальное — превью. Один флаг --prod, который выбирается по запушенному ref.

Пусть дашборд показывает коммит, а не хеш

Первые деплои из хука показывались в дашборде случайной строкой вроде AowEm2XzF вместо сообщения коммита. Когда я вычитал деплои обратно через API, причина стала ясна: у того деплоя, где сообщение отображалось, в метаданных были githubOrg, githubRepo и githubDeployment. CLI добавляет их, только когда распознаёт удалённый репозиторий GitHub в обычном чекауте; из отдельного worktree я получал ключи gitCommit* и больше ничего.

Решение — передать их самому через -m, вычислив из URL удалённого репозитория, как это делает хук выше. Сообщение, ветка и автор тогда берутся из этих ключей. Это то, что я наблюдал на своём проекте; Vercel эти ключи не документирует, поэтому после первого деплоя проверь свой дашборд и относись к ним как к тому, что может измениться.

Аутентификация: используй токен, не шарь логин

Хук вызывает vercel deploy, так что машине нужны учётные данные аккаунта, которому принадлежит команда. CLI сам читает VERCEL_TOKEN из окружения:

# A token created for this purpose; the CLI reads it automatically
export VERCEL_TOKEN="..."

Лучше токен, чем vercel login. Токен можно отозвать отдельно; общий логин нельзя отобрать у одного человека, не поменяв его для всех. К тому же именно токены Vercel документирует для автоматизации.

Раздать команде

Закоммить в репозиторий сам скрипт и установщик на одну команду, чтобы никто не копировал файлы руками:

pnpm add -g vercel
vercel link --project my-project --scope <team-slug>
bash scripts/vercel-deploy/install.sh   # copies the hook into .git/hooks

Сам хук лежит в каталоге .git/hooks, который git никогда не пушит, — поэтому и нужен установщик. Одну строку нужно поменять, прежде чем переиспользовать скрипт: шаблон URL репозитория в case в самом начале.

Сравнение с путём через CI

Это не улучшенная во всём версия прошлого поста. Здесь охват обменян на простоту:

GitHub Actions (прошлый пост)pre-push хук (этот пост)
Нужны права админа на репозиторий (секреты)ДаНет
Деплоит пуши всехДаТолько с машин, где стоит хук
Переписывает автора в раннереДа (amend, никогда не пушится)Нет
НастройкаОдин раз на репозиторийОдин раз на разработчика
Пуши из веб-интерфейса GitHub, от ботов, с других машинДеплоятсяНе деплоятся
Где видны сбоиЛог ActionsЛокальный лог-файл

Где граница

Хук — это обходной путь, а не индульгенция. Прежде чем его внедрять, будь честен с собой в трёх вещах:

  • Hobby — для некоммерческого использования. Если это продукт компании, настоящая проблема — план, а хук лишь прячет её. Решение — Pro; хук выигрывает время, пока кто-то не одобрит счёт.
  • Не шарь логин, чтобы не платить за seat-ы. Один аккаунт с деплой-токеном команды — это нормально. Несколько людей, входящих как один и тот же человек ради доступа к дашборду, — это шаринг доступа.
  • Он деплоит только то, что ты пушишь со своей машины. Ничто не мешает коллеге без хука запушить и оставить прод устаревшим — реши, кто отвечает за деплои.

Главное

  • Git-интеграция Vercel для приватного репозитория организации на Hobby отпадает: никакое разрешение не снимет ограничение плана.
  • CLI авторизует токен, а не автора коммита, поэтому проверка членства к нему не применяется.
  • Хук pre-push деплоит с машины разработчика, не требует секретов репозитория и никогда не переписывает коммит.
  • Дождись пуша и перед деплоем проверь через git ls-remote, что он дошёл.
  • Деплой worktree на запушенном SHA и передавай метаданные github* сам, чтобы дашборд показывал коммит.
  • Используй токен, а не общий логин — и считай Hobby временной мерой, если проект коммерческий.
  • Вход через GitHub привязывается только к одному аккаунту Vercel одновременно — не переноси свой на сервисный аккаунт; хуку он не нужен.

Путь через CI по-прежнему лучший вариант по умолчанию, когда можно добавлять секреты. Хук — на тот день, когда нельзя: несколько десятков строк на shell, настройка один раз на разработчика — и у каждого коммита по-прежнему его настоящий автор.

Заметили ошибку?

Неверный факт, кривой перевод, что-то звучит неправдой в этой статье? Напишите мне — на своём языке.