Деплой на Vercel із git pre-push хука: репо в організації, план Hobby, без CI-секретів
Git-інтеграція Vercel відмовляється підключати приватне репо GitHub-організації на плані Hobby, а CI-шлях вимагає секретів у репо, які тобі можуть не дозволити додати. Ось легший варіант: локальний pre-push хук, який деплоїть саме запушений коміт через Vercel CLI, а всі й далі пушать під власним іменем.
Репо переїхало в 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-інтеграцією
- Немає GitHub login connection. Vercel-акаунт, що володіє командою, ніколи не підключав GitHub, тож підключення репо падає з першою помилкою вище. Лікується в Account Settings → Authentication — але подумай, перш ніж клікати: GitHub-акаунт можна підключити лише до одного Vercel-акаунта водночас. Підключивши свій до сервісного, ти забираєш його зі свого особистого Vercel, і Vercel починає блокувати деплої твоїх особистих проєктів («Git author must have access to the project»). Хуку нижче GitHub-підключення не потрібне взагалі, тож роби це, лише якщо йдеш у Git-інтеграцію.
- Для встановлення застосунку потрібен власник організації. Vercel GitHub App треба поставити на організацію. Звичайний учасник може лише надіслати запит, а сторінка налаштувань відповідає «This action must be performed by an organization owner».
- 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 по рядку на кожен запушений реф.
#!/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, який обирається з запушеного рефа.
Зробити так, щоб дашборд показував коміт, а не хеш
Мої перші деплої з хука виглядали в дашборді як випадковий рядок на кшталт AowEm2XzF замість повідомлення коміту. Прочитавши деплої назад через API, я побачив причину: той, що показував повідомлення, мав у метаданих githubOrg, githubRepo і githubDeployment. CLI додає їх лише коли розпізнає GitHub-ремоут зі звичайного чекауту, а з detached 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-и. Один акаунт із деплой-токеном команди — нормально. Кілька людей, що заходять як одна й та сама особа заради доступу до дашборду, — це credential sharing.
- Деплоїться лише те, що ти пушиш зі своєї машини. Ніщо не заважає колезі без хука запушити й лишити продакшн застарілим — вирішіть, хто відповідає за деплої.
Головне
- Git-інтеграція Vercel для приватного репо організації на Hobby відпадає: жодні права не лікують ліміт плану.
- CLI авторизує токен, а не автора коміту, тож перевірка членства до нього не застосовується.
pre-pushхук деплоїть з машини розробника, не потребує секретів у репо й ніколи не переписує коміт.- Дочекайся пушу й перевір, що він долетів, через
git ls-remoteперед деплоєм. - Деплой worktree запушеного SHA і передавай метадані
github*самому, щоб дашборд показував коміт. - Токен, а не спільний логін, і вважай Hobby тимчасовим рішенням, якщо проєкт комерційний.
- GitHub-логін підключається лише до одного Vercel-акаунта водночас — не переносьте свій на сервісний акаунт; хуку він не потрібен.
CI-шлях і далі кращий за замовчуванням, коли можеш додати секрети. Хук — на той день, коли не можеш: кілька десятків рядків shell, налаштованих раз на розробника, і кожен коміт досі несе справжнього автора.
Помітили помилку?
Невірний факт, кривий переклад, щось звучить неправдиво в цій статті? Напишіть мені — своєю мовою.