--- title: "Cómo desplegar con Git hooks en Ubuntu 24.04" description: "Usa Git hooks para validar commits en local con pre-commit y commit-msg y para desplegar en tu servidor Ubuntu 24.04 con un repositorio bare y un post-receive." category: "Herramientas de Automatización" language: es source: https://cubepath.com/es/docs/herramientas-de-automatización/git-hooks-para-despliegue-automatico updated: 2026-09-25 --- # Cómo desplegar con Git hooks en Ubuntu 24.04 Los Git hooks son scripts que Git ejecuta automáticamente en momentos concretos: antes de crear un commit, al escribir su mensaje, antes de un push o cuando un repositorio remoto recibe cambios. En este tutorial usarás los hooks del lado del cliente para bloquear commits con errores y los del lado del servidor para desplegar un sitio web con un simple `git push` a un servidor con Ubuntu 24.04. Es un flujo sencillo, sin sistema de CI, adecuado para sitios estáticos y proyectos pequeños. ## Requisitos previos Para seguir esta guía necesitas: - Git instalado en tu equipo local y un repositorio con el código que quieres desplegar. - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo` al que te conectes por SSH con clave. - Nginx instalado en el servidor y sirviendo un sitio desde `/var/www/your_domain`, si quieres comprobar el despliegue en el navegador. A lo largo de la guía, sustituye `your_user` por tu usuario del servidor, `your_server_ip` por su IP y `your_domain` por tu dominio. ## Paso 1: Entender cómo funcionan los hooks Cada repositorio tiene un directorio `.git/hooks` con ejemplos que terminan en `.sample`. Para activar un hook basta con crear un archivo ejecutable con el nombre exacto del evento. Git le pasa información por argumentos o por la entrada estándar y, en los hooks que empiezan por `pre-`, un código de salida distinto de cero cancela la operación. Estos son los hooks que usarás: | Hook | Dónde se ejecuta | Cuándo | Puede cancelar | |---|---|---|---| | `pre-commit` | Local | Antes de crear el commit | Sí | | `commit-msg` | Local | Tras escribir el mensaje, recibe su ruta en `$1` | Sí | | `pre-push` | Local | Antes de enviar al remoto | Sí | | `post-receive` | Servidor | Después de aceptar un push, recibe las referencias por stdin | No | El directorio `.git/hooks` no se versiona, así que los hooks que pongas ahí no llegan al resto del equipo. En el siguiente paso usarás un directorio versionado. ## Paso 2: Crear un hook pre-commit compartido En tu equipo local, dentro del repositorio, crea un directorio `.githooks` e indica a Git que lo use en lugar de `.git/hooks`: ```bash mkdir -p .githooks git config core.hooksPath .githooks ``` `core.hooksPath` es una opción local de cada clon: cada miembro del equipo debe ejecutar el comando `git config` una vez tras clonar. Crea el hook `pre-commit`: ```bash nano .githooks/pre-commit ``` ```bash #!/usr/bin/env bash set -euo pipefail # Rechaza espacios en blanco erróneos y marcadores de conflicto sin resolver git diff --cached --check # Revisa con ShellCheck los scripts de shell incluidos en el commit mapfile -t scripts < <(git diff --cached --name-only --diff-filter=ACM -- '*.sh') if (( ${#scripts[@]} > 0 )); then if command -v shellcheck > /dev/null; then shellcheck "${scripts[@]}" else echo "pre-commit: shellcheck no está instalado, se omite la revisión" >&2 fi fi ``` `git diff --cached --check` es una comprobación integrada de Git que falla si el contenido preparado tiene espacios al final de línea o restos como `<<<<<<<` de un conflicto. Después, el hook revisa los scripts `.sh` añadidos o modificados. Ten en cuenta que ShellCheck analiza la versión del archivo en disco, que coincide con la preparada salvo que hayas hecho cambios después de `git add`. Hazlo ejecutable: ```bash chmod +x .githooks/pre-commit ``` Pruébalo con un archivo que tenga un espacio al final de una línea: ```bash printf 'hola \n' > prueba.txt git add prueba.txt git commit -m "test: probar el hook" ``` ```output prueba.txt:1: trailing whitespace. +hola ``` El commit no se ha creado. Corrige el archivo o elimínalo del índice con `git rm --cached prueba.txt` y borra `prueba.txt`. Si en algún momento necesitas saltarte los hooks locales, `git commit --no-verify` lo permite; por eso los hooks locales ayudan, pero no sustituyen a una validación en el servidor o en CI. ## Paso 3: Validar el formato de los mensajes con commit-msg Un formato de mensaje consistente facilita leer el historial y generar changelogs. Este hook exige el estilo Conventional Commits (`tipo(ámbito): descripción`) en la primera línea del mensaje. Crea el archivo: ```bash nano .githooks/commit-msg ``` ```bash #!/usr/bin/env bash set -euo pipefail first_line="$(head -n 1 "$1")" pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9._-]+\))?!?: .+' # Permite los mensajes que genera Git para merges y reverts if [[ "$first_line" =~ ^(Merge|Revert) ]]; then exit 0 fi if ! [[ "$first_line" =~ $pattern ]]; then echo "commit-msg: el mensaje no sigue el formato 'tipo(ámbito): descripción'" >&2 echo " Ejemplo: fix(nginx): corregir la redirección a HTTPS" >&2 exit 1 fi ``` Hazlo ejecutable y compruébalo con un mensaje incorrecto y otro correcto: ```bash chmod +x .githooks/commit-msg git commit --allow-empty -m "cambios varios" ``` ```output commit-msg: el mensaje no sigue el formato 'tipo(ámbito): descripción' Ejemplo: fix(nginx): corregir la redirección a HTTPS ``` ```bash git commit --allow-empty -m "chore: activar hooks del repositorio" ``` ```output [main 3f2a1c9] chore: activar hooks del repositorio ``` Añade los hooks al repositorio para que el resto del equipo los reciba: ```bash git add .githooks git commit -m "chore: añadir git hooks compartidos" ``` ## Paso 4: Crear un repositorio bare en el servidor Para desplegar con `git push` necesitas en el servidor un repositorio bare, es decir, sin directorio de trabajo, que solo almacena el historial. El hook `post-receive` copiará después los archivos al directorio que sirve Nginx. Conéctate al servidor: ```bash ssh your_user@your_server_ip ``` Instala Git si no lo está y crea el repositorio bare con `main` como rama inicial: ```bash sudo apt update sudo apt install git git init --bare --initial-branch=main ~/site.git ``` ```output Initialized empty Git repository in /home/your_user/site.git/ ``` Da a tu usuario la propiedad del directorio de destino para que el hook pueda escribir en él sin `sudo`: ```bash sudo mkdir -p /var/www/your_domain sudo chown your_user:your_user /var/www/your_domain ``` ## Paso 5: Escribir el hook post-receive Git ejecuta `post-receive` una vez por push y le pasa por la entrada estándar una línea por cada referencia actualizada, con el formato `revisión_antigua revisión_nueva nombre_referencia`. El hook solo desplegará cuando se actualice la rama `main`. Créalo en el servidor: ```bash nano ~/site.git/hooks/post-receive ``` ```bash #!/usr/bin/env bash set -euo pipefail readonly TARGET="/var/www/your_domain" readonly REPO="$HOME/site.git" readonly BRANCH="main" while read -r _ newrev ref; do if [[ "$ref" != "refs/heads/$BRANCH" ]]; then echo "Recibida $ref: no se despliega (solo $BRANCH)" continue fi echo "Desplegando $BRANCH (${newrev:0:7}) en $TARGET" git --work-tree="$TARGET" --git-dir="$REPO" checkout --force "$BRANCH" echo "Despliegue completado" done ``` `git checkout --force` con `--work-tree` extrae los archivos de la rama en `TARGET`. El repositorio bare recuerda qué archivos extrajo la última vez, así que los que borres del repositorio también desaparecen del destino. La revisión antigua no se necesita, por eso se lee en la variable descartable `_`. Hazlo ejecutable: ```bash chmod +x ~/site.git/hooks/post-receive ``` Si tu proyecto necesita un paso de compilación (por ejemplo `npm ci && npm run build`), ejecútalo en el hook después del `checkout`, con `cd "$TARGET"` delante, y apunta Nginx al directorio de salida. ## Paso 6: Desplegar con git push Vuelve a tu equipo local y añade el servidor como remoto con el nombre `production`: ```bash git remote add production your_user@your_server_ip:site.git ``` Envía la rama `main`: ```bash git push production main ``` Las líneas que empiezan por `remote:` son la salida del hook en el servidor: ```output Enumerating objects: 12, done. Counting objects: 100% (12/12), done. Writing objects: 100% (12/12), 3.41 KiB | 3.41 MiB/s, done. Total 12 (delta 1), reused 0 (delta 0), pack-reused 0 remote: Desplegando main (8c41e02) en /var/www/your_domain remote: Already on 'main' remote: Despliegue completado To your_server_ip:site.git * [new branch] main -> main ``` Comprueba en el servidor que los archivos están en su sitio y que Nginx los sirve: ```bash ssh your_user@your_server_ip ls /var/www/your_domain curl -I http://your_domain ``` ```output HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) Content-Type: text/html ``` A partir de ahora, cada `git push production main` publica la última versión. Ten en cuenta que se publica todo el contenido del repositorio, incluido `.githooks`: no guardes en él secretos ni archivos que no deban ser accesibles desde la web, o configura Nginx para servir solo un subdirectorio como `public/`. Para volver a una versión anterior, revierte el commit problemático con `git revert` y vuelve a hacer push. ## Paso 7: Reiniciar un servicio tras el despliegue (opcional) Si despliegas una aplicación que corre como servicio de systemd, el hook debe reiniciarla. En lugar de dar a tu usuario `sudo` sin contraseña para todo, permítele solo ese comando. Crea una regla con `visudo`, que valida la sintaxis antes de guardar: ```bash sudo visudo -f /etc/sudoers.d/deploy-your_app ``` ```text your_user ALL=(root) NOPASSWD: /usr/bin/systemctl restart your_app.service ``` Añade esta línea en el hook `post-receive`, justo después del `checkout`: ```bash sudo /usr/bin/systemctl restart your_app.service ``` Comprueba que la regla funciona sin pedir contraseña: ```bash sudo -n /usr/bin/systemctl restart your_app.service && echo "reinicio permitido" ``` ## Solución de problemas **`hint: The '.githooks/pre-commit' hook was ignored because it's not set as executable.`**: falta el permiso de ejecución. Aplica `chmod +x` al hook. En Windows, marca el bit en Git con `git update-index --chmod=+x .githooks/pre-commit`. **El hook no se ejecuta después de clonar**: `core.hooksPath` no se clona. Ejecuta `git config core.hooksPath .githooks` en el nuevo clon. **`/usr/bin/env: 'bash\r': No such file or directory`**: el hook tiene finales de línea CRLF. Conviértelo con `sed -i 's/\r$//' ruta/del/hook`. **`remote: fatal: this operation must be run in a work tree`** o el push se rechaza con `refusing to update checked out branch`: el remoto no es un repositorio bare o el hook no pasa `--work-tree`. Crea el repositorio con `git init --bare` y revisa el comando `checkout` del hook. **`Permission denied` al escribir en `/var/www/your_domain`**: el directorio no pertenece al usuario con el que haces push. Revisa el `chown` del paso 4. ## Conclusión Has configurado hooks locales compartidos que bloquean commits con errores y mensajes sin formato, y un repositorio bare con un hook `post-receive` que despliega tu sitio en Ubuntu 24.04 con cada `git push`. Como siguientes pasos puedes: - Desplegar en un directorio nuevo por versión y cambiar un enlace simbólico al final, para que el cambio sea atómico y el rollback inmediato. - Añadir un hook `pre-receive` en el servidor que rechace pushes que no pasen las pruebas, ya que los hooks locales se pueden saltar. - Pasar a un sistema de CI/CD como GitHub Actions o GitLab CI cuando el proyecto necesite compilación, pruebas y varios entornos.