Deploy til Vercel fra en git pre-push-hook: org-repo, Hobby-plan, ingen CI-secrets
På Hobby-planen afviser Vercels Git-integration et privat repo, der ejes af en GitHub-organisation, og CI-vejen kræver repo-secrets, som du måske ikke må tilføje. Her er den lettere løsning: en lokal pre-push-hook, der deployer præcis den pushede commit med Vercel-CLI'en, mens alle fortsat pusher under eget navn.
Repoet flyttede ind i en GitHub-organisation, Vercel-projektet ligger i et Hobby-team, og alle på projektet pusher under deres eget GitHub-navn. Jeg ville have, at hvert push deployer — main til produktion, alle andre grene til en preview — uden at ændre, hvem committene tilhører.
Min første plan var den indbyggede Git-integration. Den fejlede tre gange i træk, hver gang af en anden grund:
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)Tre mure mellem dig og Git-integrationen
- Ingen GitHub-loginforbindelse. Vercel-kontoen, der ejer teamet, havde aldrig forbundet GitHub, så linkningen af repoet fejler med den første fejl ovenfor. Løsningen er Account Settings → Authentication — men tænk dig om, før du klikker: en GitHub-konto kan kun være forbundet til én Vercel-konto ad gangen. Forbinder du din til servicekontoen, flyttes den væk fra din personlige Vercel, og Vercel blokerer derefter deploys af dine personlige projekter («Git author must have access to the project»). Hooken nedenfor kræver slet ingen GitHub-forbindelse, så gør det kun, hvis du går efter Git-integrationen.
- Installation af appen kræver en organisationsejer. Vercel GitHub App'en skal installeres på organisationen. Et almindeligt medlem kan kun anmode om det, og indstillingssiden svarer «This action must be performed by an organization owner».
- Hobby kan ikke forbinde private organisationsrepos. Med alt ovenstående på plads svarer Vercel stadig 409. Ingen indstilling løser den her: planen er grænsen.
De to første er tilladelser, du kan jagte. Den tredje betyder, at Git-integrationen er udelukket, indtil teamet er på Pro. Alt herefter er en workaround — så vælg en, der er ærlig om at være en workaround.
Deploy fra den maskine, der pusher
Vercel-CLI'en autentificerer med et login eller et token, ikke med commit-forfatteren, så den kører aldrig medlemskabstjekket, der blokerer integrationen. Mit forrige indlæg kører CLI'en i GitHub Actions. Det kræver VERCEL_TOKEN i repoets secrets, hvilket betyder admin-rettigheder på repoet — plus en --amend af forfatteren i runneren, så dashboardet viser en commit.
En pre-push-hook springer begge dele over. Den kører på udviklerens maskine efter git push, deployer den commit, der netop blev pushet, og rører aldrig committen: den rigtige forfatter bliver stående på GitHub og i dashboardet. Prisen er, at den findes pr. maskine — mere om det nedenfor.
Hooken
Gem den som .git/hooks/pre-push, og gør den eksekverbar. Git kalder den med remote-navnet og URL'en og sender den én linje pr. pushet ref på 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 0Fire detaljer bærer det hele:
- Match remote-URL'en, ikke navnet. Git sender URL'en som
$2. Ved et push til en hvilken som helst anden remote afslutter hooken med det samme, så et mirror eller en fork deployer aldrig. - Vent på pushet, og tjek så, at det landede. Hooken kører før pushet er færdigt, og et afvist push kører den også. Derfor kobler den sig fri, venter på, at
git push-processen er afsluttet, og deployer kun, hvisgit ls-remoteviser din SHA på remoten. - Deploy den pushede commit, ikke dit working tree. Et midlertidigt
git worktreeved den pushede SHA sikrer, at ucommittede ændringer og halvfærdige filer aldrig går live. Det genbruger dit.vercel-link. mainer produktion, alt andet er en preview. Ét--prod-flag, valgt ud fra den pushede ref.
Få dashboardet til at vise committen, ikke en hash
Mine første hook-deploys dukkede op i dashboardet som en tilfældig streng som AowEm2XzF i stedet for commit-beskeden. Da jeg læste deploysene tilbage gennem API'en, kunne jeg se hvorfor: det deploy, der viste en besked, havde githubOrg, githubRepo og githubDeployment i sine metadata. CLI'en tilføjer dem kun, når den genkender en GitHub-remote fra et normalt checkout — fra et detached worktree fik jeg gitCommit*-nøgler og intet andet.
Løsningen er selv at sende dem med -m, udledt af remote-URL'en, som hooken ovenfor gør. Besked, gren og forfatter kommer så fra de nøgler. Det er det, jeg har observeret på mit eget projekt; Vercel dokumenterer ikke disse nøgler, så tjek dit dashboard efter det første deploy, og behandl dem som noget, der kan ændre sig.
Autentificering: brug et token, del ikke et login
Hooken kalder vercel deploy, så maskinen skal have legitimationsoplysninger til den konto, der ejer teamet. CLI'en læser selv VERCEL_TOKEN fra miljøet:
# A token created for this purpose; the CLI reads it automatically
export VERCEL_TOKEN="..."Foretræk et token frem for vercel login. Et token kan tilbagekaldes for sig selv; et delt login kan ikke inddrages fra én person uden at ændre det for alle. Tokens er også det, Vercel dokumenterer til automatisering.
Del den med teamet
Commit scriptet og en installer på én kommando til repoet, så ingen kopierer filer i hånden:
pnpm add -g vercel
vercel link --project my-project --scope <team-slug>
bash scripts/vercel-deploy/install.sh # copies the hook into .git/hooksSelve hooken ligger i .git/hooks, som git aldrig pusher — derfor findes installeren. Én linje skal du ændre, før du genbruger scriptet: repo-URL-mønsteret i case øverst.
Sammenlignet med CI-vejen
Det her er ikke entydigt en bedre version af det forrige indlæg. Den bytter rækkevidde ud med enkelhed:
| GitHub Actions (forrige indlæg) | pre-push-hook (dette indlæg) | |
|---|---|---|
| Kræver admin på repoet (secrets) | Ja | Nej |
| Deployer alles pushes | Ja | Kun fra maskiner med hooken |
| Omskriver forfatteren i runneren | Ja (amend, aldrig pushet) | Nej |
| Opsætning | Én gang pr. repo | Én gang pr. udvikler |
| Pushes fra GitHubs web-UI, bots, andre maskiner | Deployet | Ikke deployet |
| Hvor fejl dukker op | Actions-loggen | En lokal logfil |
Hvor grænsen går
En hook er en workaround, ikke en fribillet. Før du tager den i brug, skal du være ærlig over for dig selv om tre ting:
- Hobby er til ikke-kommerciel brug. Hvis det her er en virksomheds produkt, er planen det egentlige problem, og hooken skjuler det bare. Løsningen er Pro; hooken køber tid, indtil nogen godkender fakturaen.
- Del ikke et login for at undgå at betale for pladser. Én konto, der holder teamets deploy-token, er fint. At flere personer logger ind som den samme person for at få dashboard-adgang er deling af login.
- Den deployer kun det, du pusher fra din maskine. Intet forhindrer en kollega uden hooken i at pushe og efterlade produktionen forældet — beslut, hvem der ejer deploys.
Pointer
- Vercels Git-integration er udelukket for et privat organisationsrepo på Hobby: ingen tilladelse løser en begrænsning i planen.
- CLI'en autentificerer et token, ikke commit-forfatteren, så medlemskabstjekket gælder ikke for den.
- En
pre-push-hook deployer fra udviklerens maskine, kræver ingen repo-secrets og omskriver aldrig en commit. - Vent på pushet, og bekræft med
git ls-remote, at det landede, før du deployer. - Deploy et worktree af den pushede SHA, og send selv
github*-metadataene med, så dashboardet viser committen. - Brug et token, ikke et delt login — og se Hobby som en nødløsning, hvis projektet er kommercielt.
- Et GitHub-login kan kun være forbundet til én Vercel-konto ad gangen — flyt ikke dit til servicekontoen; hooken har ikke brug for det.
CI-vejen er stadig det bedre standardvalg, når du kan tilføje secrets. Hooken er til den dag, du ikke kan: et par dusin linjer shell, sat op én gang pr. udvikler, og hver commit bærer stadig sin rigtige forfatter.
Fandt du en fejl?
Et forkert faktum, en skæv oversættelse, noget der virker usandt i denne artikel? Skriv til mig — på dit eget sprog.