Git Hooks para Despliegue Automático: Automatización CI/CD con Git
Introducción
Los Git hooks son scripts poderosos que se ejecutan automáticamente en puntos específicos del flujo de trabajo de Git, habilitando pruebas automatizadas, verificaciones de calidad de código y pipelines de despliegue. Al aprovechar los Git hooks, puedes implementar prácticas de integración y despliegue continuo directamente dentro de tu sistema de control de versiones, asegurando la calidad del código y automatizando tareas repetitivas.
Esta guía completa explora implementaciones prácticas de Git hooks para despliegue automático, validación pre-commit y automatización CI/CD.
Entendiendo Git Hooks
¿Qué son los Git Hooks?
Los Git hooks son scripts personalizados que Git ejecuta antes o después de eventos como commit, push, merge y checkout. Te permiten automatizar flujos de trabajo, hacer cumplir políticas y disparar despliegues automáticamente.
Tipos de Git Hooks
Hooks del Lado del Cliente:
pre-commit: Se ejecuta antes de confirmar cambiosprepare-commit-msg: Prepara la plantilla del mensaje de commitcommit-msg: Valida mensajes de commitpost-commit: Se ejecuta después de que se completa el commitpre-rebase: Se ejecuta antes de hacer rebasepost-checkout: Se ejecuta después de hacer checkout de una ramapost-merge: Se ejecuta después de hacer mergepre-push: Se ejecuta antes de hacer push al remoto
Hooks del Lado del Servidor:
pre-receive: Se ejecuta antes de aceptar commits enviadosupdate: Se ejecuta para cada rama que se está actualizandopost-receive: Se ejecuta después de recibir y desempaquetar objetospost-update: Se ejecuta después de actualizar referencias remotas
Ubicación de Hooks
# Project hooks
.git/hooks/
# Global hooks (Git 2.9+)
git config --global core.hooksPath ~/.git-hooks
# List available hooks
ls -la .git/hooks/
Pre-Commit Hooks
1. Linting y Formateo de Código
#!/bin/bash
# .git/hooks/pre-commit
# Ensure code quality before commits
set -e
echo "Running pre-commit checks..."
# Check if there are files to commit
if git diff --cached --name-only | grep -qE '\\.(js|jsx|ts|tsx)$'; then
echo "Running ESLint..."
npm run lint
echo "Running Prettier..."
npm run format:check
fi
# Python files
if git diff --cached --name-only | grep -qE '\\.py$'; then
echo "Running Black..."
black --check .
echo "Running Flake8..."
flake8 .
echo "Running mypy..."
mypy .
fi
# Shell scripts
if git diff --cached --name-only | grep -qE '\\.sh$'; then
echo "Running shellcheck..."
git diff --cached --name-only | grep '\\.sh$' | xargs shellcheck
fi
echo "✓ Pre-commit checks passed"
2. Escaneo de Seguridad
#!/bin/bash
# .git/hooks/pre-commit
# Scan for secrets and security issues
set -e
# Check for AWS keys
if git diff --cached | grep -qiE 'AKIA[0-9A-Z]{16}'; then
echo "❌ AWS Access Key detected in commit"
exit 1
fi
# Check for private keys
if git diff --cached | grep -qiE '-----BEGIN (RSA|DSA|EC) PRIVATE KEY-----'; then
echo "❌ Private key detected in commit"
exit 1
fi
# Check for common secrets
if git diff --cached | grep -qiE 'password\\s*=|api_key\\s*=|secret\\s*='; then
echo "⚠️ Possible hardcoded credentials detected"
echo "Please review your changes carefully"
fi
# Run gitleaks if available
if command -v gitleaks &> /dev/null; then
echo "Running gitleaks..."
gitleaks protect --staged
fi
echo "✓ Security checks passed"
3. Ejecución de Pruebas
#!/bin/bash
# .git/hooks/pre-commit
# Run tests before allowing commit
set -e
echo "Running unit tests..."
# Node.js projects
if [ -f "package.json" ]; then
npm test
fi
# Python projects
if [ -f "pytest.ini" ] || [ -f "setup.py" ]; then
pytest
fi
# Go projects
if [ -f "go.mod" ]; then
go test ./...
fi
echo "✓ All tests passed"
Validación de Mensajes de Commit
4. Forzar Formato de Mensaje de Commit
#!/bin/bash
# .git/hooks/commit-msg
# Enforce conventional commit message format
commit_msg_file=$1
commit_msg=$(cat "$commit_msg_file")
# Conventional commits pattern
pattern='^(feat|fix|docs|style|refactor|test|chore)(\\(.+\\))?: .{1,100}$'
if ! echo "$commit_msg" | grep -qE "$pattern"; then
cat <<EOF
❌ Invalid commit message format
Commit messages must follow the conventional commits format:
<type>(<scope>): <subject>
Examples:
feat(api): add user authentication endpoint
fix(ui): resolve button alignment issue
docs: update installation instructions
Types: feat, fix, docs, style, refactor, test, chore
EOF
exit 1
fi
echo "✓ Commit message format is valid"
Pre-Push Hooks
5. Validación Pre-Push
#!/bin/bash
# .git/hooks/pre-push
# Validate before pushing to remote
set -e
echo "Running pre-push checks..."
# Get current branch
current_branch=$(git branch --show-current)
# Prevent pushing to main/master directly
if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
echo "❌ Direct push to $current_branch is not allowed"
echo "Please create a feature branch and submit a pull request"
exit 1
fi
# Run full test suite
echo "Running full test suite..."
npm test || pytest || go test ./...
# Check for merge conflicts markers
if git grep -qE '<<<<<<< HEAD|>>>>>>> |=======' -- ':!.git'; then
echo "❌ Merge conflict markers detected"
exit 1
fi
echo "✓ Pre-push checks passed"
Hooks del Lado del Servidor para Despliegue
6. Post-Receive Hook para Despliegue Automático
#!/bin/bash
# hooks/post-receive
# Automatic deployment after receiving push
set -e
DEPLOY_DIR="/var/www/production"
REPO_DIR="/var/repos/myapp.git"
BRANCH="main"
while read oldrev newrev refname; do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "$branch" = "$BRANCH" ]; then
echo "Deploying $branch to production..."
# Create backup
BACKUP_DIR="/var/backups/deployments"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
if [ -d "$DEPLOY_DIR" ]; then
tar -czf "$BACKUP_DIR/backup-${TIMESTAMP}.tar.gz" -C "$DEPLOY_DIR" .
echo "✓ Backup created: backup-${TIMESTAMP}.tar.gz"
fi
# Checkout code
mkdir -p "$DEPLOY_DIR"
GIT_WORK_TREE="$DEPLOY_DIR" git checkout -f "$BRANCH"
# Install dependencies
cd "$DEPLOY_DIR"
if [ -f "package.json" ]; then
echo "Installing npm dependencies..."
npm install --production
fi
if [ -f "requirements.txt" ]; then
echo "Installing Python dependencies..."
pip install -r requirements.txt
fi
# Run database migrations
if [ -f "manage.py" ]; then
python manage.py migrate --noinput
fi
# Build assets
if [ -f "package.json" ] && grep -q "\\"build\\"" package.json; then
npm run build
fi
# Restart application
echo "Restarting application..."
systemctl restart myapp
# Verify deployment
sleep 5
if systemctl is-active --quiet myapp; then
echo "✓ Deployment successful"
# Send notification
curl -X POST "https://hooks.slack.com/services/YOUR/WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{\\\"text\\\":\\\"✓ Deployment successful: ${newrev:0:7} to production\\\"}"
else
echo "❌ Deployment failed - service not running"
# Rollback
echo "Rolling back to previous version..."
latest_backup=$(ls -t "$BACKUP_DIR" | head -1)
tar -xzf "$BACKUP_DIR/$latest_backup" -C "$DEPLOY_DIR"
systemctl restart myapp
# Send failure notification
curl -X POST "https://hooks.slack.com/services/YOUR/WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{\\\"text\\\":\\\"❌ Deployment failed: rolling back\\\"}"
exit 1
fi
fi
done
echo "Post-receive hook completed"
7. Zero-Downtime Deployment Hook
#!/bin/bash
# hooks/post-receive
# Zero-downtime deployment with health checks
set -e
DEPLOY_DIR="/var/www/production"
RELEASE_DIR="/var/www/releases"
CURRENT_LINK="/var/www/current"
BRANCH="main"
while read oldrev newrev refname; do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "$branch" = "$BRANCH" ]; then
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RELEASE_PATH="$RELEASE_DIR/$TIMESTAMP"
echo "Creating release: $TIMESTAMP"
mkdir -p "$RELEASE_PATH"
# Checkout code to new release directory
GIT_WORK_TREE="$RELEASE_PATH" git checkout -f "$BRANCH"
cd "$RELEASE_PATH"
# Install dependencies
npm install --production
# Build application
npm run build
# Link shared directories (uploads, logs, etc.)
ln -s "$DEPLOY_DIR/shared/uploads" "$RELEASE_PATH/public/uploads"
ln -s "$DEPLOY_DIR/shared/logs" "$RELEASE_PATH/logs"
# Update current symlink
ln -sfn "$RELEASE_PATH" "$CURRENT_LINK"
# Reload application (graceful restart)
systemctl reload myapp
# Health check
echo "Performing health check..."
sleep 5
if curl -f http://localhost:3000/health > /dev/null 2>&1; then
echo "✓ Health check passed"
# Cleanup old releases (keep last 5)
cd "$RELEASE_DIR"
ls -t | tail -n +6 | xargs -r rm -rf
echo "✓ Deployment completed successfully"
else
echo "❌ Health check failed - rolling back"
# Rollback to previous release
PREVIOUS_RELEASE=$(ls -t "$RELEASE_DIR" | sed -n '2p')
ln -sfn "$RELEASE_DIR/$PREVIOUS_RELEASE" "$CURRENT_LINK"
systemctl reload myapp
# Cleanup failed release
rm -rf "$RELEASE_PATH"
exit 1
fi
fi
done
Gestión de Git Hooks
Instalación de Hooks
#!/bin/bash
# install-hooks.sh
# Install Git hooks for all developers
HOOKS_DIR=".githooks"
GIT_HOOKS_DIR=".git/hooks"
# Copy hooks to .git/hooks
for hook in "$HOOKS_DIR"/*; do
hook_name=$(basename "$hook")
cp "$hook" "$GIT_HOOKS_DIR/$hook_name"
chmod +x "$GIT_HOOKS_DIR/$hook_name"
echo "Installed: $hook_name"
done
# Configure Git to use custom hooks directory
git config core.hooksPath "$HOOKS_DIR"
echo "✓ Git hooks installed successfully"
Compartir Hooks con el Equipo
# Store hooks in repository
mkdir -p .githooks
# Move hooks to .githooks directory
mv .git/hooks/pre-commit .githooks/
mv .git/hooks/commit-msg .githooks/
# Configure git to use .githooks directory
git config core.hooksPath .githooks
# Add to repository
git add .githooks
git commit -m "chore: add git hooks for team"
# Team members run:
git config core.hooksPath .githooks
chmod +x .githooks/*
Patrones Avanzados de Despliegue
8. Blue-Green Deployment Hook
#!/bin/bash
# hooks/post-receive
# Blue-green deployment pattern
set -e
BLUE_DIR="/var/www/blue"
GREEN_DIR="/var/www/green"
CURRENT_LINK="/var/www/current"
BRANCH="main"
# Determine which environment is currently active
if [ -L "$CURRENT_LINK" ]; then
CURRENT=$(readlink "$CURRENT_LINK")
if [ "$CURRENT" = "$BLUE_DIR" ]; then
TARGET_DIR="$GREEN_DIR"
TARGET_PORT=3001
CURRENT_PORT=3000
else
TARGET_DIR="$BLUE_DIR"
TARGET_PORT=3000
CURRENT_PORT=3001
fi
else
TARGET_DIR="$BLUE_DIR"
TARGET_PORT=3000
fi
echo "Deploying to $(basename $TARGET_DIR) environment..."
# Deploy to target environment
GIT_WORK_TREE="$TARGET_DIR" git checkout -f "$BRANCH"
cd "$TARGET_DIR"
# Install and build
npm install --production
npm run build
# Start application on target port
PORT=$TARGET_PORT npm start &
PID=$!
# Wait for application to start
sleep 10
# Health check
if curl -f "http://localhost:$TARGET_PORT/health" > /dev/null 2>&1; then
echo "✓ New version is healthy"
# Switch traffic
ln -sfn "$TARGET_DIR" "$CURRENT_LINK"
# Update load balancer or reverse proxy
# Update Nginx upstream
sed -i "s/localhost:$CURRENT_PORT/localhost:$TARGET_PORT/" /etc/nginx/sites-enabled/myapp
nginx -s reload
# Stop old version
OLD_PID=$(lsof -ti:$CURRENT_PORT)
if [ -n "$OLD_PID" ]; then
kill $OLD_PID
fi
echo "✓ Traffic switched to new version"
else
echo "❌ Health check failed"
kill $PID
exit 1
fi
Mejores Prácticas
Plantilla de Hook
#!/bin/bash
# Git Hook Template
set -euo pipefail
# Colors for output
RED='\\033[0;31m'
GREEN='\\033[0;32m'
YELLOW='\\033[1;33m'
NC='\\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $*"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $*"
}
# Main logic here
exit 0
Probar Hooks
# Test pre-commit hook
git commit --allow-empty -m "test" --no-verify # Skip hooks
git commit --allow-empty -m "test" # Run hooks
# Test post-receive hook
# Push to test repository with hook installed
git push test-repo main
Depurar Hooks
#!/bin/bash
# Add to beginning of hook
# Enable debug mode
set -x
# Log to file
exec 2>> /tmp/git-hook-debug.log
Conclusión
Los Git hooks proporcionan capacidades poderosas de automatización para hacer cumplir la calidad del código, ejecutar pruebas y desplegar aplicaciones automáticamente. Al implementar estos hooks, puedes crear pipelines CI/CD robustos directamente dentro de tu flujo de trabajo de Git.
Puntos clave:
- Usa hooks del lado del cliente para calidad de código y pruebas
- Implementa hooks del lado del servidor para despliegue automático
- Siempre incluye verificaciones de salud y mecanismos de rollback
- Comparte hooks con tu equipo vía repositorio
- Prueba los hooks exhaustivamente antes de uso en producción
- Implementa manejo de errores y registro adecuados
- Documenta el comportamiento de los hooks para los miembros del equipo
Comienza con hooks simples y gradualmente agrega automatización más sofisticada a medida que tu equipo se sienta cómodo con el flujo de trabajo.


