Desplegar en Vercel desde un hook pre-push de git: repo de organización, plan Hobby, sin secretos de CI
La integración de Git de Vercel rechaza un repo privado de una organización de GitHub en el plan Hobby, y la vía de CI necesita secretos del repo que quizá no te dejen añadir. Esta es la opción más ligera: un hook pre-push local que despliega con la CLI de Vercel exactamente el commit que acabas de subir, mientras todos siguen haciendo push con su propio nombre.
El repo se mudó a una organización de GitHub, el proyecto de Vercel está en un equipo Hobby, y todos en el proyecto hacen push con su propio nombre de GitHub. Quería que cada push se desplegara — main a producción, cualquier otra rama a un preview — sin cambiar a quién pertenecen los commits.
Mi primer plan fue la integración nativa de Git. Falló tres veces seguidas, cada una por un motivo distinto:
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)Tres muros entre tú y la integración de Git
- Sin conexión de login de GitHub. La cuenta de Vercel dueña del equipo nunca había conectado GitHub, así que vincular el repo falla con el primer error de arriba. La solución es Account Settings → Authentication — pero piénsalo antes de hacer clic: una cuenta de GitHub solo puede estar conectada a una cuenta de Vercel a la vez. Conectar la tuya a la cuenta de servicio la desvincula de tu Vercel personal, y Vercel pasa a bloquear los despliegues de tus proyectos personales («Git author must have access to the project»). El hook de abajo no necesita ninguna conexión con GitHub, así que hazlo solo si vas a por la integración de Git.
- Para instalar la app hace falta un propietario de la organización. La GitHub App de Vercel tiene que instalarse en la organización. Un miembro normal solo puede solicitarla, y la página de ajustes responde «This action must be performed by an organization owner».
- Hobby no puede conectar repos privados de organizaciones. Con todo lo anterior hecho, Vercel sigue respondiendo 409. Ningún ajuste lo arregla: el límite es el plan.
Los dos primeros son permisos que puedes perseguir. El tercero significa que la integración de Git queda descartada hasta que el equipo pase a Pro. Todo lo que sigue es un apaño, así que elige uno que no oculte que lo es.
Desplegar desde la máquina que hace push
La CLI de Vercel se autentica con un login o un token, no con el autor del commit, así que nunca ejecuta la comprobación de pertenencia que bloquea la integración. Mi artículo anterior ejecuta la CLI en GitHub Actions. Eso necesita VERCEL_TOKEN en los secretos del repo, o sea, permisos de administrador sobre el repo — más un --amend del autor en el runner para que el panel muestre un commit.
Un hook pre-push se ahorra ambas cosas. Se ejecuta en la máquina del desarrollador después de git push, despliega el commit que acaba de subirse y nunca toca el commit: el autor real se queda en GitHub y en el panel. El precio es que hay que tenerlo en cada máquina — más sobre eso abajo.
El hook
Guárdalo como .git/hooks/pre-push y hazlo ejecutable. Git lo llama con el nombre y la URL del remoto, y le pasa por stdin una línea por cada ref que se sube.
#!/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 0Cuatro detalles sostienen todo:
- Compara la URL del remoto, no su nombre. Git pasa la URL como
$2. Un push a cualquier otro remoto termina al instante, así que un mirror o un fork nunca se despliega. - Espera al push y luego comprueba que llegó. El hook se ejecuta antes de que termine el push, y un push rechazado también lo ejecuta. Por eso se separa en segundo plano, espera a que el proceso de
git pushtermine y solo despliega sigit ls-remotemuestra tu SHA en el remoto. - Despliega el commit subido, no tu árbol de trabajo. Un
git worktreedesechable en el SHA subido hace que los cambios sin commitear y los archivos a medias nunca se publiquen. Reutiliza tu vínculo de.vercel. maines producción, todo lo demás es un preview. Un único flag--prod, elegido según la ref subida.
Haz que el panel muestre el commit, no un hash
Los despliegues de mi primer hook aparecían en el panel como una cadena aleatoria del tipo AowEm2XzF en lugar del mensaje del commit. Consultar los despliegues por la API mostró el motivo: el que sí mostraba mensaje llevaba githubOrg, githubRepo y githubDeployment en sus metadatos. La CLI los añade solo cuando reconoce un remoto de GitHub desde un checkout normal — desde un worktree en modo detached obtuve claves gitCommit* y nada más.
La solución es pasarlos tú mismo con -m, derivados de la URL del remoto, como hace el hook de arriba. Mensaje, rama y autor salen entonces de esas claves. Esto es lo que observé en mi propio proyecto; Vercel no documenta estas claves, así que revisa tu panel después del primer despliegue y trátalas como algo que puede cambiar.
Autenticación: usa un token, no compartas un login
El hook llama a vercel deploy, así que la máquina necesita credenciales de la cuenta dueña del equipo. La CLI lee VERCEL_TOKEN del entorno por sí sola:
# A token created for this purpose; the CLI reads it automatically
export VERCEL_TOKEN="..."Prefiere un token a vercel login. Un token se puede revocar por separado; un login compartido no se le puede quitar a una persona sin cambiarlo para todos. Además, los tokens son lo que Vercel documenta para la automatización.
Compartirlo con el equipo
Commitea el script y un instalador de un solo comando en el repo, para que nadie copie archivos a mano:
pnpm add -g vercel
vercel link --project my-project --scope <team-slug>
bash scripts/vercel-deploy/install.sh # copies the hook into .git/hooksEl hook en sí vive en .git/hooks, que git nunca sube — por eso existe el instalador. Una línea que cambiar antes de reutilizar el script: el patrón de la URL del repo en el case del principio.
Cómo se compara con la vía de CI
Esta no es una versión estrictamente mejor del artículo anterior. Cambia alcance por simplicidad:
| GitHub Actions (artículo anterior) | hook pre-push (este artículo) | |
|---|---|---|
| Necesita admin en el repo (secretos) | Sí | No |
| Despliega los pushes de todos | Sí | Solo desde máquinas con el hook |
| Reescribe el autor en el runner | Sí (amend, nunca se sube) | No |
| Configuración | Una vez por repo | Una vez por desarrollador |
| Pushes desde la web de GitHub, bots, otras máquinas | Se despliegan | No se despliegan |
| Dónde aparecen los fallos | Log de Actions | Un archivo de log local |
Dónde está la línea
Un hook es un apaño, no una licencia. Antes de adoptarlo, sé sincero contigo mismo sobre tres cosas:
- Hobby es para uso no comercial. Si esto es el producto de una empresa, el verdadero problema es el plan y el hook solo lo esconde. La solución es Pro; el hook gana tiempo hasta que alguien apruebe la factura.
- No compartas un login para evitar pagar asientos. Una cuenta que guarda el token de despliegue del equipo está bien. Varias personas iniciando sesión como el mismo usuario para tener acceso al panel es compartir credenciales.
- Solo despliega lo que subes desde tu máquina. Nada impide que un compañero sin el hook haga push y deje producción desactualizada — decide quién se encarga de los despliegues.
Conclusiones
- La integración de Git de Vercel queda descartada para un repo privado de organización en Hobby: ningún permiso arregla un límite del plan.
- La CLI autentica un token, no al autor del commit, así que la comprobación de pertenencia no le afecta.
- Un hook
pre-pushdespliega desde la máquina del desarrollador, no necesita secretos del repo y nunca reescribe un commit. - Espera al push y comprueba con
git ls-remoteque llegó antes de desplegar. - Despliega un worktree del SHA subido y pasa tú mismo los metadatos
github*para que el panel muestre el commit. - Usa un token, no un login compartido — y trata Hobby como una solución provisional si el proyecto es comercial.
- Un login de GitHub se conecta a una sola cuenta de Vercel a la vez — no muevas el tuyo a la cuenta de servicio; el hook no lo necesita.
La vía de CI sigue siendo la mejor opción por defecto cuando puedes añadir secretos. El hook es para el día en que no puedes: unas pocas docenas de líneas de shell, configuradas una vez por desarrollador, y cada commit conserva su autor real.
¿Ves un error?
¿Un dato incorrecto, una traducción torpe, algo que suena falso en este artículo? Escríbeme — en tu propio idioma.