¿Cómo enviar Repositorios Git directamente a tu Servidor Linux? (sin plataformas CI)

En algún momento me di cuenta de que casi todos los proyectos seguían el mismo ritual..

  1. Hacer push del código a GitHub
  2. Disparar un pipeline de CI
  3. El CI descarga el código
  4. El servidor de producción vuelve a descargar el mismo código

Cuanto más lo pensaba, más redundante me parecía. GitHub se había convertido en un intermediario entre máquinas que ya poseía y controlaba.

Git ya proporciona:

  • Control de versiones distribuido
  • Transporte seguro mediante SSH
  • Hooks
  • Ramas (branching)
  • Colaboración

SSH añade autenticación, autorización y auditoría.

Entonces me hice una pregunta simple: ¿Por qué enviar el código a GitHub solo para que mi propio servidor lo vuelva a descargar?

Aquí te explico cómo reemplazar ese flujo por un modelo de despliegue directo usando Git sobre SSH.

Entendiendo el modelo nativo de Git

Muchos desarrolladores olvidan que Git fue diseñado desde el principio para flujos de trabajo distribuidos.

Cuando ejecutas:

git clone user@server:/srv/git/myapp.git

Git:

  • Abre una conexión SSH
  • Ejecuta git-upload-pack remotamente
  • Transmite los objetos vía stdin/stdout

GitHub es, esencialmente:

Un servidor SSH ejecutando Git con una interfaz web y control de acceso, que una vez interiorizas este modelo mental, el autoalojamiento se vuelve sencillo.

🔨 Parte 1: Convirtiendo tu servidor Linux en un servidor Git

Entorno utilizado

  • Ubuntu 22.04 LTS
  • Acceso SSH
  • Git instalado en cliente y servidor

Paso 1: Crear un usuario dedicado para Git

En el servidor:

sudo adduser git

Luego desactiva el acceso interactivo a la shell:

sudo chsh -s /usr/sbin/nologin git
# o
sudo chsh -s /bin/false git

Esto permite operaciones Git vía SSH pero evita acceso completo a la shell.

Prueba:

ssh git@demo-lab

Salida esperada:

This account is currently not available

Paso 2: Añadir acceso SSH

En el servidor:

sudo vim /home/git/.ssh/authorized_keys

Pega tu clave pública del cliente.

Configuración opcional en el cliente:

Host demo-lab
    HostName 192.168.1.50
    User git

Ahora el acceso Git queda limitado a un único usuario sin privilegios.

Paso 3: Crear un repositorio bare

El servidor no necesita un working tree, solo los datos de Git.

sudo mkdir -p /srv/git
cd /srv/git
sudo git init --bare myapp.git

Corrige permisos:

sudo chown -R git:git /srv/git/myapp.git

Tu servidor ahora es un servidor Git completamente funcional.

🔨 Parte 2: Enviar código directamente al servidor

En tu máquina local:

git remote add production git@demo-lab:/srv/git/myapp.git
git push production master

Y listo.

Sin OAuth.
Sin tokens.
Sin runners de CI.
Solo Git sobre SSH.


Automatizando el despliegue con Git Hooks

Dentro del repositorio bare:

cd /srv/git/myapp.git/hooks
vim post-receive

Ejemplo de hook de despliegue:

#!/usr/bin/bash

APP_DIR=/var/www/myapp
GIT_DIR=/srv/git/myapp.git

while read oldrev newrev ref; do
  if [[ "$ref" =~ .*/master$ ]]; then
    echo "Desplegando rama master..."

    git \
      --work-tree=$APP_DIR \
      --git-dir=$GIT_DIR \
      checkout -f
  fi
done

Hazlo ejecutable:

chmod +x post-receive

Ahora cada git push despliega automáticamente.

🔨 Parte 3: CI/CD simple sin servicios externos

Clona el directorio de trabajo:

cd /var/www/
git clone /srv/git/myapp.git myapp

Si aparece:

fatal: detected dubious ownership

Solución:

git config --global --add safe.directory /srv/git/myapp.git

A partir de ahora:

git push → despliegue automático

Modelo mental simple:

Push → Deploy


Flujo multi-desarrollador sin GitHub

Restringe claves SSH en authorized_keys:

command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"",
no-port-forwarding,no-X11-forwarding
ssh-ed25519 AAAA...

Los desarrolladores pueden:

git push production feature-x
git push production main

Los hooks pueden:

  • Rechazar pushes directos a main
  • Forzar convenciones de nombres
  • Ejecutar tests antes de aceptar código

La colaboración funciona igual que en GitHub — solo sin interfaz web.


Problemas comunes (y soluciones)

1️⃣ Problemas de permisos

sudo chown -R git:git /srv/git
sudo chown -R deploy:deploy /var/www/myapp

Sé explícito con la propiedad de archivos.

2️⃣ Hooks que no se ejecutan

Causas comunes:

  • No es ejecutable
  • Shebang incorrecto
  • Rutas relativas

Buenas prácticas:

  • Usa rutas absolutas
  • Añade set -e
  • Registra todo en logs
exec >> /var/log/git-deploy.log 2>&1

3️⃣ Despliegues largos bloquean el push

Git espera a que el hook termine.

Mejor alternativa:

systemctl start deploy-myapp.service

Deja que systemd gestione el despliegue de forma asíncrona.


Buenas prácticas en producción

  • Mantén los hooks pequeños
  • Delegar tareas pesadas a systemd o scripts externos
  • Registrar siempre la salida de los hooks
  • Usar remotos separados:
origin → desarrollo
production → servidor de despliegue

Así evitas pushes accidentales a producción.


¿Cuándo tiene sentido este enfoque?

✅ Equipos pequeños (2–10 personas)
✅ Infraestructura completamente autoalojada
✅ Entornos centrados en seguridad
✅ Preferencia por mínima complejidad


¿Cuándo no es recomendable?

❌ Proyectos open-source
❌ Flujos intensivos de Pull Requests
❌ Stakeholders no técnicos necesitan dashboards
❌ Procesos complejos de aprobación

GitHub es infraestructura opcional – no un requisito de Git.


Conclusión

Git ya proporciona:

  • Control de versiones distribuido
  • Transporte seguro (SSH)
  • Hooks
  • Ramas
  • Colaboración

Cuando dejas de tratar GitHub como “el flujo de trabajo” y lo ves como un servicio opcional, todo se simplifica.

En muchos entornos de producción, el pipeline más simple es:

git push → el servidor sabe qué hacer

Sin YAML.
Sin runners.
Sin dependencias externas.

Solo Git haciendo lo que fue diseñado para hacer.