En algún momento me di cuenta de que casi todos los proyectos seguían el mismo ritual..
- Hacer push del código a GitHub
- Disparar un pipeline de CI
- El CI descarga el código
- 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-packremotamente - 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.
