Déployer sur Vercel depuis un hook git pre-push : dépôt d'organisation, plan Hobby, aucun secret de CI
L'intégration Git de Vercel refuse un dépôt privé appartenant à une organisation GitHub sur le plan Hobby, et la voie CI demande des secrets de dépôt que tu n'as peut-être pas le droit d'ajouter. Voici l'option plus légère : un hook pre-push local qui déploie exactement le commit poussé avec la CLI Vercel, pendant que chacun continue de pousser sous son propre nom.
Le dépôt est passé dans une organisation GitHub, le projet Vercel vit dans une équipe Hobby, et chaque personne du projet pousse sous son propre nom GitHub. Je voulais que chaque push déploie — main en production, toute autre branche en preview — sans changer à qui appartiennent les commits.
Mon premier plan, c'était l'intégration Git native. Elle a échoué trois fois de suite, à chaque fois pour une raison différente :
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)Trois murs entre toi et l'intégration Git
- Aucun login GitHub connecté. Le compte Vercel propriétaire de l'équipe n'avait jamais connecté GitHub, donc le lien avec le dépôt échoue avec la première erreur ci-dessus. La solution, c'est Account Settings → Authentication — mais réfléchis avant de cliquer : un compte GitHub ne peut être connecté qu'à un seul compte Vercel à la fois. Connecter le tien au compte de service le retire de ton Vercel personnel, et Vercel bloque ensuite les déploiements de tes projets personnels (« Git author must have access to the project »). Le hook ci-dessous n'a besoin d'aucune connexion GitHub, donc ne fais ça que si tu vises l'intégration Git.
- Installer l'app demande un propriétaire de l'organisation. La GitHub App de Vercel doit être installée sur l'organisation. Un simple membre ne peut que la demander, et la page de réglages répond « This action must be performed by an organization owner ».
- Hobby ne peut pas connecter des dépôts privés d'organisation. Une fois tout ce qui précède fait, Vercel répond toujours 409. Aucun réglage n'y change rien : la limite, c'est le plan.
Les deux premiers sont des permissions que tu peux aller chercher. Le troisième veut dire que l'intégration Git est exclue tant que l'équipe n'est pas en Pro. Tout ce qui suit est un contournement — alors choisis-en un qui assume honnêtement d'en être un.
Déployer depuis la machine qui pousse
La CLI Vercel s'authentifie avec un login ou un token, pas avec l'auteur du commit, donc elle n'exécute jamais la vérification d'appartenance qui bloque l'intégration. Mon article précédent fait tourner la CLI dans GitHub Actions. Ça demande VERCEL_TOKEN dans les secrets du dépôt, donc des droits d'admin sur le dépôt — plus un --amend de l'auteur dans le runner pour que le tableau de bord affiche un commit.
Un hook pre-push évite les deux. Il tourne sur la machine du développeur après git push, déploie le commit qui vient d'être poussé, et ne touche jamais au commit : le vrai auteur reste sur GitHub et dans le tableau de bord. Le prix, c'est qu'il est propre à chaque machine — j'y reviens plus bas.
Le hook
Enregistre-le sous .git/hooks/pre-push et rends-le exécutable. Git l'appelle avec le nom et l'URL du remote, et lui envoie sur stdin une ligne par ref poussée.
#!/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 0Quatre détails portent tout le poids :
- Filtre sur l'URL du remote, pas sur son nom. Git passe l'URL dans
$2. Pour un push vers n'importe quel autre remote, le hook sort immédiatement, donc un miroir ou un fork ne déploie jamais. - Attends le push, puis vérifie qu'il est arrivé. Le hook tourne avant la fin du push, et un push rejeté le fait tourner aussi. Donc il se détache, attend que le processus
git pushse termine, et ne déploie que sigit ls-remotemontre ton SHA sur le remote. - Déploie le commit poussé, pas ton arbre de travail. Un
git worktreejetable au SHA poussé garantit que les modifications non commitées et les fichiers à moitié finis ne partent jamais en ligne. Il réutilise ton lien.vercel. mainest la production, tout le reste est une preview. Un seul flag--prod, choisi d'après la ref poussée.
Fais afficher le commit dans le tableau de bord, pas un hash
Mes premiers déploiements par le hook apparaissaient dans le tableau de bord comme une chaîne aléatoire du genre AowEm2XzF au lieu du message de commit. Relire les déploiements via l'API a montré pourquoi : celui qui affichait un message portait githubOrg, githubRepo et githubDeployment dans ses métadonnées. La CLI ne les ajoute que lorsqu'elle reconnaît un remote GitHub depuis un checkout normal — depuis un worktree détaché, je n'obtenais que des clés gitCommit* et rien d'autre.
La solution est de les passer toi-même avec -m, déduites de l'URL du remote, comme le fait le hook ci-dessus. Message, branche et auteur viennent alors de ces clés. C'est ce que j'ai observé sur mon propre projet ; Vercel ne documente pas ces clés, donc vérifie ton tableau de bord après le premier déploiement et considère qu'elles peuvent changer.
Authentification : utilise un token, ne partage pas de login
Le hook appelle vercel deploy, donc la machine a besoin des identifiants du compte propriétaire de l'équipe. La CLI lit VERCEL_TOKEN dans l'environnement toute seule :
# A token created for this purpose; the CLI reads it automatically
export VERCEL_TOKEN="..."Préfère un token à vercel login. Un token peut être révoqué indépendamment ; un login partagé ne peut pas être retiré à une personne sans le changer pour tout le monde. Les tokens sont aussi ce que Vercel documente pour l'automatisation.
Le partager avec l'équipe
Commite le script et un installateur en une commande dans le dépôt, pour que personne ne copie de fichiers à la main :
pnpm add -g vercel
vercel link --project my-project --scope <team-slug>
bash scripts/vercel-deploy/install.sh # copies the hook into .git/hooksLe hook lui-même vit dans .git/hooks, que git ne pousse jamais — c'est pour ça que l'installateur existe. Une ligne à changer avant de réutiliser le script : le motif d'URL du dépôt dans le case tout en haut.
Comparaison avec la voie CI
Ce n'est pas une version strictement meilleure de l'article précédent. On échange de la portée contre de la simplicité :
| GitHub Actions (article précédent) | Hook pre-push (cet article) | |
|---|---|---|
| Demande des droits d'admin sur le dépôt (secrets) | Oui | Non |
| Déploie les pushs de tout le monde | Oui | Seulement depuis les machines avec le hook |
| Réécrit l'auteur dans le runner | Oui (amend, jamais poussé) | Non |
| Mise en place | Une fois par dépôt | Une fois par développeur |
| Pushs depuis l'interface web de GitHub, bots, autres machines | Déployés | Non déployés |
| Où apparaissent les échecs | Log d'Actions | Un fichier de log local |
Où est la limite
Un hook est un contournement, pas un blanc-seing. Avant de l'adopter, sois honnête avec toi-même sur trois points :
- Hobby est réservé à un usage non commercial. Si c'est le produit d'une entreprise, le vrai problème est le plan, et le hook ne fait que le masquer. La solution, c'est Pro ; le hook fait gagner du temps jusqu'à ce que quelqu'un valide la facture.
- Ne partage pas de login pour éviter de payer des sièges. Un compte qui détient le token de déploiement de l'équipe, ça ne pose aucun problème. Plusieurs personnes qui se connectent sous la même identité pour accéder au tableau de bord, c'est du partage d'identifiants.
- Il ne déploie que ce que tu pousses depuis ta machine. Rien n'empêche un coéquipier sans le hook de pousser et de laisser la production périmée — décide qui est responsable des déploiements.
À retenir
- L'intégration Git de Vercel est exclue pour un dépôt privé d'organisation sur Hobby : aucune permission ne corrige une limite de plan.
- La CLI authentifie un token, pas l'auteur du commit, donc la vérification d'appartenance ne la concerne pas.
- Un hook
pre-pushdéploie depuis la machine du développeur, n'a besoin d'aucun secret de dépôt et ne réécrit jamais un commit. - Attends le push et vérifie qu'il est arrivé avec
git ls-remoteavant de déployer. - Déploie un worktree du SHA poussé, et passe toi-même les métadonnées
github*pour que le tableau de bord affiche le commit. - Utilise un token, pas un login partagé — et considère Hobby comme un palliatif si le projet est commercial.
- Un login GitHub se connecte à un seul compte Vercel à la fois — ne déplace pas le tien vers le compte de service ; le hook n'en a pas besoin.
La voie CI reste le meilleur choix par défaut quand tu peux ajouter des secrets. Le hook est pour le jour où tu ne peux pas : quelques dizaines de lignes de shell, installées une fois par développeur, et chaque commit garde son vrai auteur.
Vous voyez une erreur ?
Un fait erroné, une traduction bancale, quelque chose qui sonne faux dans cet article ? Écrivez-moi — dans votre propre langue.