Auf Vercel aus einem git-pre-push-Hook deployen: Org-Repo, Hobby-Plan, keine CI-Secrets
Vercels Git-Integration lehnt ein privates Repo einer GitHub-Organisation im Hobby-Plan ab, und der CI-Weg braucht Repo-Secrets, die du vielleicht nicht anlegen darfst. Hier die leichtere Variante: ein lokaler pre-push-Hook, der genau den gepushten Commit mit der Vercel-CLI deployt, während alle weiter unter ihrem eigenen Namen pushen.
Das Repo zog in eine GitHub-Organisation um, das Vercel-Projekt liegt in einem Hobby-Team, und alle im Projekt pushen unter ihrem eigenen GitHub-Namen. Ich wollte, dass jeder Push deployt — main in die Produktion, jeder andere Branch als Preview —, ohne zu ändern, wem die Commits gehören.
Mein erster Plan war die native Git-Integration. Sie scheiterte dreimal hintereinander, jedes Mal aus einem anderen 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)Drei Mauern zwischen dir und der Git-Integration
- Kein GitHub-Login verbunden. Das Vercel-Konto, dem das Team gehört, hatte GitHub nie verbunden, deshalb scheitert das Verknüpfen des Repos mit dem ersten Fehler oben. Die Lösung ist Account Settings → Authentication — aber überleg, bevor du klickst: Ein GitHub-Konto kann immer nur mit einem Vercel-Konto gleichzeitig verbunden sein. Verbindest du deins mit dem Service-Konto, wandert es von deinem persönlichen Vercel weg, und Vercel blockiert dann Deploys deiner persönlichen Projekte („Git author must have access to the project“). Der Hook unten braucht gar keine GitHub-Verbindung, also tu das nur, wenn du wirklich die Git-Integration willst.
- Für die Installation der App braucht es einen Organisations-Owner. Die Vercel-GitHub-App muss in der Organisation installiert werden. Ein normales Mitglied kann sie nur beantragen, und die Einstellungsseite antwortet mit „This action must be performed by an organization owner“.
- Hobby kann keine privaten Organisations-Repos verbinden. Auch wenn alles oben erledigt ist, antwortet Vercel weiter mit 409. Keine Einstellung behebt das: Der Plan ist die Grenze.
Die ersten beiden sind Berechtigungen, denen du hinterherlaufen kannst. Die dritte heißt: Die Git-Integration ist vom Tisch, bis das Team auf Pro ist. Alles Weitere ist ein Workaround — such dir also einen aus, der ehrlich damit umgeht, ein Workaround zu sein.
Von der Maschine deployen, die pusht
Die Vercel-CLI authentifiziert sich mit einem Login oder einem Token, nicht mit dem Commit-Autor, deshalb führt sie die Mitgliedschaftsprüfung nie aus, die die Integration blockiert. Mein vorheriger Beitrag lässt die CLI in GitHub Actions laufen. Das braucht VERCEL_TOKEN in den Secrets des Repos, also Admin-Rechte auf dem Repo — plus ein --amend des Autors im Runner, damit das Dashboard einen Commit zeigt.
Ein pre-push-Hook erspart beides. Er läuft auf der Maschine des Entwicklers nach git push, deployt den soeben gepushten Commit und fasst den Commit nie an: Der echte Autor bleibt auf GitHub und im Dashboard erhalten. Der Preis: Er existiert pro Maschine — dazu unten mehr.
Der Hook
Speichere ihn als .git/hooks/pre-push und mach ihn ausführbar. Git ruft ihn mit Remote-Name und URL auf und füttert ihn über stdin mit einer Zeile pro gepushter Ref.
#!/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 0Vier Details tragen das Ganze:
- Die Remote-URL prüfen, nicht ihren Namen. Git übergibt die URL als
$2. Ein Push an irgendein anderes Remote beendet den Hook sofort, sodass ein Mirror oder ein Fork nie deployt. - Auf den Push warten, dann prüfen, ob er angekommen ist. Der Hook läuft bevor der Push fertig ist, und auch ein abgelehnter Push startet ihn. Deshalb koppelt er sich ab, wartet, bis der
git push-Prozess beendet ist, und deployt nur, wenngit ls-remotedeinen SHA auf dem Remote zeigt. - Den gepushten Commit deployen, nicht dein Arbeitsverzeichnis. Ein Wegwerf-
git worktreeauf dem gepushten SHA sorgt dafür, dass nicht committete Änderungen und halbfertige Dateien nie ausgeliefert werden. Er nutzt deine bestehende.vercel-Verknüpfung weiter. mainist Produktion, alles andere ein Preview. Ein einziges--prod-Flag, gewählt anhand der gepushten Ref.
Das Dashboard soll den Commit zeigen, nicht einen Hash
Die Deploys meines ersten Hooks erschienen im Dashboard als zufälliger String wie AowEm2XzF statt als Commit-Nachricht. Die Deployments über die API zurückzulesen zeigte, warum: Das Deployment, das eine Nachricht anzeigte, trug githubOrg, githubRepo und githubDeployment in seinen Metadaten. Die CLI ergänzt diese nur, wenn sie aus einem normalen Checkout ein GitHub-Remote erkennt — aus einem losgelösten Worktree bekam ich gitCommit*-Keys und sonst nichts.
Die Lösung: Gib sie selbst mit -m mit, abgeleitet aus der Remote-URL, wie es der Hook oben tut. Nachricht, Branch und Autor kommen dann aus diesen Keys. So habe ich es in meinem eigenen Projekt beobachtet; Vercel dokumentiert diese Keys nicht. Prüfe also nach dem ersten Deploy dein Dashboard und behandle sie als etwas, das sich ändern kann.
Authentifizierung: ein Token nutzen, keinen Login teilen
Der Hook ruft vercel deploy auf, also braucht die Maschine Zugangsdaten für das Konto, dem das Team gehört. Die CLI liest VERCEL_TOKEN von selbst aus der Umgebung:
# A token created for this purpose; the CLI reads it automatically
export VERCEL_TOKEN="..."Nimm lieber ein Token statt vercel login. Ein Token lässt sich einzeln widerrufen; einen geteilten Login kann man einer Person nicht wegnehmen, ohne ihn für alle zu ändern. Tokens sind außerdem das, was Vercel für Automatisierung dokumentiert.
Mit dem Team teilen
Committe das Skript und einen Ein-Befehl-Installer ins Repo, damit niemand Dateien von Hand kopiert:
pnpm add -g vercel
vercel link --project my-project --scope <team-slug>
bash scripts/vercel-deploy/install.sh # copies the hook into .git/hooksDer Hook selbst liegt in .git/hooks, das git nie pusht — deshalb gibt es den Installer. Eine Zeile musst du ändern, bevor du das Skript wiederverwendest: das Muster der Repo-URL im case ganz oben.
Der Vergleich mit dem CI-Weg
Das ist keine durchweg bessere Version des vorherigen Beitrags. Der Hook tauscht Reichweite gegen Einfachheit:
| GitHub Actions (vorheriger Beitrag) | pre-push-Hook (dieser Beitrag) | |
|---|---|---|
| Braucht Admin-Rechte auf dem Repo (Secrets) | Ja | Nein |
| Deployt die Pushes aller | Ja | Nur von Maschinen mit dem Hook |
| Schreibt den Autor im Runner um | Ja (Amend, nie gepusht) | Nein |
| Einrichtung | Einmal pro Repo | Einmal pro Entwickler |
| Pushes aus der GitHub-Weboberfläche, von Bots, von anderen Maschinen | Werden deployt | Werden nicht deployt |
| Wo Fehler sichtbar werden | Actions-Log | Eine lokale Logdatei |
Wo die Grenze liegt
Ein Hook ist ein Workaround, kein Freibrief. Bevor du ihn einsetzt, sei in drei Punkten ehrlich zu dir selbst:
- Hobby ist für nicht-kommerzielle Nutzung. Wenn das hier das Produkt einer Firma ist, ist der Plan das eigentliche Problem, und der Hook versteckt es nur. Die Lösung heißt Pro; der Hook verschafft Zeit, bis jemand die Rechnung freigibt.
- Teile keinen Login, um Seats zu sparen. Ein Konto, das das Deploy-Token des Teams hält, ist in Ordnung. Wenn sich mehrere Leute als dieselbe Person einloggen, um Dashboard-Zugriff zu bekommen, ist das Zugangsdaten-Teilen.
- Er deployt nur, was du von deiner Maschine pushst. Nichts hindert einen Kollegen ohne den Hook daran, zu pushen und die Produktion veraltet zurückzulassen — entscheide, wer für die Deploys verantwortlich ist.
Fazit
- Vercels Git-Integration ist für ein privates Organisations-Repo im Hobby-Plan vom Tisch: Keine Berechtigung behebt ein Plan-Limit.
- Die CLI authentifiziert ein Token, nicht den Commit-Autor, deshalb greift die Mitgliedschaftsprüfung bei ihr nicht.
- Ein
pre-push-Hook deployt von der Maschine des Entwicklers, braucht keine Repo-Secrets und schreibt nie einen Commit um. - Warte auf den Push und prüfe mit
git ls-remote, dass er angekommen ist, bevor du deployst. - Deploye einen Worktree des gepushten SHA und übergib die
github*-Metadaten selbst, damit das Dashboard den Commit zeigt. - Nutze ein Token, keinen geteilten Login — und behandle Hobby als Übergangslösung, wenn das Projekt kommerziell ist.
- Ein GitHub-Login ist immer nur mit einem Vercel-Konto gleichzeitig verbunden — verschieb deinen nicht auf das Service-Konto; der Hook braucht ihn nicht.
Der CI-Weg bleibt der bessere Standard, wenn du Secrets anlegen kannst. Der Hook ist für den Tag, an dem du es nicht kannst: ein paar Dutzend Zeilen Shell, einmal pro Entwickler eingerichtet, und jeder Commit trägt weiterhin seinen echten Autor.
Einen Fehler entdeckt?
Ein falscher Fakt, eine schiefe Übersetzung, etwas, das in diesem Artikel falsch klingt? Schreib mir — in deiner eigenen Sprache.