🇪🇸 Español
6 min

Cómo hacer despliegues automáticos (CI/CD) con GitHub Actions usando un Mini PC en local (Self-Hosted Runner)

Aprende a automatizar tus despliegues a un Mini PC en tu red local con GitHub Actions y Self-Hosted Runner: sin agotar minutos de GitHub, con máxima seguridad sin abrir puertos y usando Docker.

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 .env y el docker-compose.yml para 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.

  1. Ve a tu repositorio en GitHub > Settings > Actions > Runners > New self-hosted runner.
  2. En tu Mini PC de producción, crea una carpeta temporal (mkdir actions-runner y cd actions-runner).
  3. Ejecuta los comandos que te proporciona GitHub en la web para descargar e instalar el Runner.
  4. 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

  1. Evita la doble compilación: No hacemos un yarn build en la nube si luego vamos a hacer un docker build en el Mini PC. Ahorramos tiempo.
  2. 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.
  3. Resiliencia de Sistema: La exportación del $PATH y los comprobantes de docker-compose vs docker compose solucionan 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.