Agile Software Development
En el mundo del desarrollo moderno, la automatización de despliegues no es un lujo, es una necesidad. Tener que entrar manualmente a un servidor, hacer git pull y reiniciar servicios no solo es tedioso, sino también propenso a errores.
En este artículo te muestro un proyecto que resuelve exactamente ese problema: un webhook en Python con Flask que recibe notificaciones de GitHub y actualiza automáticamente tu aplicación en un servidor Linux.
El código completo está disponible en mi repositorio de GitHub:
🔗 DaveSV/CICD-webhook-en-Linux-con-Flask

¿Qué hace este proyecto?
La idea es simple, pero poderosa:
- Recibes un push en GitHub.
- GitHub envía un webhook a tu servidor.
- Flask valida que la petición es legítima (firma secreta).
- El servidor ejecuta
git fetch+git reset --hardpara actualizar el código. - Se reinicia el servicio web para aplicar los cambios.
Todo esto, de forma automática y sin intervención manual.
Arquitectura de la solución
Este proyecto está pensado para producción, y por eso está compuesto de varios elementos clave:
- Flask → El microframework en Python que expone el endpoint
/pull-repo. - Gunicorn → Servidor WSGI que ejecuta Flask de forma eficiente en segundo plano.
- Nginx → Servidor web que recibe las peticiones HTTP y las pasa a Gunicorn.
- systemd → Sistema de servicios de Linux que mantiene todo funcionando y reinicia si hay fallos.
- GitHub Webhooks → El mecanismo que dispara la actualización tras un push.
1️⃣ Código Python del Webhook
El corazón del sistema es webhook.py, un script en Flask que:
- Valida la firma de GitHub usando un secreto compartido.
- Actualiza el repositorio local desde la rama definida.
- Reinicia un servicio systemd para que la web se actualice.
from flask import Flask, request, abort
import subprocess, hmac, hashlib, os
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "cambia_esta_clave")
REPO_PATH = "/var/www/mi-sitio"
BRANCH = "main"
@app.route('/pull-repo', methods=['POST'])
def pull_repo():
signature = request.headers.get("X-Hub-Signature-256")
if not signature:
abort(403)
sha_name, signature = signature.split('=')
if sha_name != 'sha256':
abort(403)
mac = hmac.new(WEBHOOK_SECRET.encode(), msg=request.data, digestmod=hashlib.sha256)
if not hmac.compare_digest(mac.hexdigest(), signature):
abort(403)
try:
subprocess.run(["git", "-C", REPO_PATH, "fetch"], check=True)
subprocess.run(["git", "-C", REPO_PATH, "reset", "--hard", f"origin/{BRANCH}"], check=True)
subprocess.run(["systemctl", "restart", "mi-sitio.service"], check=True)
return "Actualización exitosa\n", 200
except subprocess.CalledProcessError as e:
return f"Error actualizando: {e}\n", 500
2️⃣ Configuración del servicio en systemd
Para que Flask se ejecute de forma estable con Gunicorn, se crea el archivo /etc/systemd/system/webhook.service:
[Unit]
Description=Webhook GitHub para actualizar el sitio
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/ruta/a/tu/app
Environment="WEBHOOK_SECRET=tu_clave_secreta"
ExecStart=/usr/bin/gunicorn --workers 2 --bind unix:/tmp/webhook.sock webhook:app
[Install]
WantedBy=multi-user.target
3️⃣ Configuración de Nginx
En /etc/nginx/sites-available/webhook.conf se define el proxy hacia Gunicorn:
server {
listen 80;
server_name tu-dominio.com;
location /pull-repo {
proxy_pass http://unix:/tmp/webhook.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
4️⃣ Configuración en GitHub

- Ir a Settings → Webhooks → Add webhook.
- Payload URL:
http://tu-dominio.com/pull-repo - Content type:
application/json - Secret: tu clave secreta (
WEBHOOK_SECRET) - Evento: Just the push event
- Guardar y probar.
5️⃣ Flujo de trabajo
Cuando hagas un git push:
- GitHub envía un POST al endpoint
/pull-repo. - Flask valida la firma.
- Se actualiza el repositorio (
git fetch+git reset --hard). - Se reinicia el servicio web (
systemctl restart mi-sitio.service).
Ventajas de este enfoque
- ✅ Automatización total → Cada push en GitHub se refleja en producción en segundos.
- ✅ Seguridad → Validación con HMAC SHA-256.
- ✅ Robustez → systemd y Gunicorn aseguran que el webhook siempre esté activo.
- ✅ Flexibilidad → Puedes adaptarlo para reiniciar cualquier servicio, no solo sitios web.
📌 Repositorio completo:
🔗 https://github.com/DaveSV/CICD-webhook-en-Linux-con-Flask
Este es un ejemplo claro de cómo un poco de código Python y una buena configuración de servidor pueden ahorrarte horas de trabajo repetitivo, convirtiendo tu flujo de despliegue en algo rápido, seguro y sin dolor.
🔹 … y GitHub Actions? Lo que hace
- Permite flujos mucho más complejos (tests, builds, lint, despliegues múltiples).
- Ejecuta pasos en runners de GitHub o propios (self-hosted runners).
- Puede desplegar a múltiples entornos o nubes (AWS, Azure, GCP, etc.).
- Aporta integración con marketplace y miles de acciones prehechas.
- Pero… requiere más configuración y depende de un servicio externo.
Una vez que tienes tu aplicación Flask corriendo detrás de Nginx y ya le has asignado un dominio, puedes generar e instalar un certificado SSL/TLS automático en el servidor usando Let’s Encrypt con Certbot.