Automatizar despliegues puede ser un quebradero de cabeza cuando no usas servicios en la nube convencionales (como AWS o Vercel), sino que tienes tu propio hardware, por ejemplo, un Mini PC en tu red local haciendo las veces de servidor de producción.
En mi caso, mi arquitectura era la siguiente:
- Máquina de Desarrollo: Donde escribo el código.
- Servidor de Producción (Mini PC): Donde corro mis aplicaciones con Docker. Aquí no guardo el código fuente, solo tengo los archivos
.envy eldocker-compose.ymlpara levantar la imagen compilada.
¿Cómo hacemos que al hacer git push desde el entorno de desarrollo, el Mini PC reciba el código, compile la imagen de Docker, y reinicie el servicio en producción de forma 100% automática?
Aquí te cuento la solución definitiva.
💎 Las grandes ventajas de esta arquitectura
Antes de meternos en el código, es importante destacar por qué este enfoque es tan potente frente a las alternativas tradicionales.
⏳ 1. Ahorro infinito en Minutos de GitHub Actions
GitHub Actions ofrece 2.000 minutos gratuitos al mes, lo cual puede quedarse corto si tienes procesos de compilación (builds) muy pesados como los de Nuxt o Node.js. El truco de oro: Los minutos que se ejecutan en máquinas Self-Hosted (tu propio ordenador) NO descuentan minutos de la cuota de GitHub. En este tutorial usaremos la nube solo unos segundos para una comprobación rápida, y le pasaremos el 95% del trabajo pesado a nuestro propio Mini PC. El resultado es que jamás agotarás tu cuota gratuita.
🛡️ 2. Seguridad Extrema (Firewall Cerrado)
Con el enfoque clásico, GitHub Actions necesitaba conectarse por SSH a tu servidor casero, obligándote a abrir el puerto 22 en el router, exponiéndote a todo internet. Al usar un Self-Hosted Runner, es tu Mini PC quien se conecta a GitHub de forma segura (HTTPS de salida) para preguntar si hay trabajo. No tienes que abrir ni un solo puerto externo hacia tu casa. Tu red se mantiene completamente hermética.
🔑 3. Gestión Segura de Contraseñas y Recuperación
Gracias a GitHub Secrets, las contraseñas del registro Docker no quedan en texto plano en el disco del servidor. Se inyectan de manera cifrada en la memoria del Mini PC solo durante la ejecución. Además, al compilar la imagen de forma automática y subirla al registro (como veremos abajo), te garantizas que si el Mini PC se quema, tu producción está a salvo y lista para descargar en cualquier otra máquina al instante.
1. Instalar el GitHub Actions Runner en tu Mini PC
Lo primero es conectar tu servidor local con GitHub. En lugar de ejecutar las acciones (Actions) en los servidores de GitHub, vamos a usar un Self-Hosted Runner.
- Ve a tu repositorio en GitHub > Settings > Actions > Runners > New self-hosted runner.
- En tu Mini PC de producción, crea una carpeta temporal (
mkdir actions-runnerycd actions-runner). - Ejecuta los comandos que te proporciona GitHub en la web para descargar e instalar el Runner.
- Muy importante: Para que el Runner funcione en segundo plano y no dependa de que tengas la terminal abierta, instálalo como servicio de
systemd:sudo ./svc.sh install sudo ./svc.sh start
A partir de ahora, tu Mini PC está escuchando órdenes de GitHub.
2. El problema clásico con el código, Docker y las rutas
Cuando GitHub Actions le dice a tu Mini PC que ejecute una tarea, descarga el código en una carpeta temporal y oculta (dentro de _work).
Esto genera un problema enorme: si intentas ejecutar docker-compose up desde esa carpeta temporal, fallará con el error no configuration file provided: not found. ¿Por qué? Porque el archivo docker-compose.yml de producción no está en tu repositorio, sino en otra carpeta de tu servidor.
Además, si lanzas comandos en tu archivo de GitHub Actions, verás que el Runner arroja errores tipo docker-compose: No existe el archivo o el directorio (Exit code 127). Esto ocurre porque, al correr como un servicio en segundo plano, el Runner tiene un entorno súper limitado y no encuentra las rutas estándar del sistema.
3. La solución elegante (ci.yml)
Para solucionar esto, no hace falta complicar la arquitectura. Simplemente, hacemos que el Runner aproveche el código descargado temporalmente para construir la imagen, la suba opcionalmente al registro (para tener backup) y luego le decimos que navegue a tu verdadera carpeta de producción para reiniciar el servicio.
En la raíz de tu repositorio, crea el archivo .github/workflows/ci.yml con este contenido:
name: CI/CD a Mini PC
on:
push:
branches: [ main, master ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 20.x
cache: 'yarn' # O 'npm'
- name: Comprobar dependencias
# Apenas gasta segundos de los 2000 gratis de GitHub
run: yarn install --frozen-lockfile --ignore-engines
deploy:
needs: build-and-test
runs-on: self-hosted # Aquí es donde ocurre la magia sin coste
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
steps:
- name: Descargar el código temporalmente
uses: actions/checkout@v3
- name: Compilar y Desplegar
run: |
# 1. Construimos la imagen Docker usando el código temporal que bajó el Runner
docker build -t mi-registro/mi-app:latest .
# Opcional (Recomendado): Subir imagen para backup seguro usando Secrets
# docker login mi-registro.com -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASSWORD }}
# docker push mi-registro/mi-app:latest
# 2. Vamos a tu carpeta REAL de producción donde está tu docker-compose.yml y tu .env
cd /ruta/absoluta/a/tu/servidor/de/produccion
# 3. Exportamos el PATH para evitar el Error 127 en servicios de sistema
export PATH=$PATH:/usr/local/bin:/usr/bin:/bin
# 4. Buscamos la versión de docker que tenga tu sistema y reiniciamos
if command -v docker-compose &> /dev/null; then
docker-compose down && docker-compose up -d
elif command -v docker &> /dev/null && docker compose version &> /dev/null; then
docker compose down && docker compose up -d
else
/usr/local/bin/docker-compose down && /usr/local/bin/docker-compose up -d
fi
🚀 Resumen de los "Fixes" mágicos de este Script
- Evita la doble compilación: No hacemos un
yarn builden la nube si luego vamos a hacer undocker builden el Mini PC. Ahorramos tiempo. - Ignora las carpetas temporales: Construye la imagen con el código temporal, pero va a la carpeta de producción a levantar el servicio. Separación limpia.
- Resiliencia de Sistema: La exportación del
$PATHy los comprobantes dedocker-composevsdocker composesolucionan de golpe el maldito error 127 de binarios no encontrados en procesos del sistema.
¡Y listo! Con esto tienes una infraestructura digna de una gran empresa corriendo en un humilde Mini PC en tu salón. Todo automático, hiper-seguro y gratuito.