Guía - CI/CD

Cómo configurar un GitHub Actions Self-Hosted Runner en Mac Mini M4

Guía paso a paso para configurar un GitHub Actions self-hosted runner en un Mac Mini M4 dedicado. Compilaciones iOS más rápidas, Apple Silicon nativo y hasta 10x más económico que los runners macOS alojados en GitHub.

30 min de lectura Actualizado en enero 2025

1. ¿Por qué usar un Self-Hosted Mac Runner?

Los runners macOS alojados en GitHub son convenientes pero costosos. A $0.08 por minuto, un equipo que ejecuta 75 horas de compilaciones al mes paga alrededor de $360. Un Mac Mini M4 dedicado de MyRemoteMac cuesta $75/mes con minutos de compilación ilimitados, dándole el mismo (o mejor) hardware por una fracción del coste.

Característica Runner alojado en GitHub MyRemoteMac Self-Hosted
Coste $0.08/min (~$350/mes por 75h) $75/mes (minutos ilimitados)
Arquitectura Intel x86 (algunos M1) Apple M4 (último modelo)
Velocidad de compilación ~12 min (proyecto mediano) ~4 min (mismo proyecto)
Caché persistente No (efímero) Sí (disco persistente)
Software personalizado Limitado Acceso root completo
Jobs concurrentes 5 (gratis) / 20 (pago) Ilimitados (su hardware)

Beneficio clave: Debido a que el runner es persistente, DerivedData, las cachés de SPM y CocoaPods se preservan entre compilaciones. Esto por sí solo puede reducir los tiempos de compilación en un 50-70% comparado con los runners efímeros alojados en GitHub que comienzan desde cero cada vez.

2. Requisitos previos

Antes de comenzar, asegúrese de tener lo siguiente:

  • Un servidor Mac Mini M4 de MyRemoteMac (desde $75/mes)
  • Una cuenta de GitHub con acceso de administrador a su repositorio u organización
  • Acceso SSH a su Mac Mini (incluido con su suscripción de MyRemoteMac)
  • Una cuenta de Apple Developer (para la firma de código y perfiles de aprovisionamiento)
  • Familiaridad básica con YAML y comandos de terminal

3. Paso 1: Conectarse por SSH al Mac Mini e instalar Xcode

Primero, conéctese a su Mac Mini M4 por SSH. Habrá recibido sus credenciales al configurar su servidor MyRemoteMac.

Conectarse por SSH

# Connect to your Mac Mini M4
ssh admin@your-server-ip

# Verify you're on Apple Silicon
uname -m
# Expected output: arm64

# Check macOS version
sw_vers
# ProductName:    macOS
# ProductVersion: 15.2
# BuildVersion:   24C101

Instalar Xcode Command Line Tools

# Install Command Line Tools
xcode-select --install

# Accept the license agreement
sudo xcodebuild -license accept

# Verify installation
xcode-select -p
# /Library/Developer/CommandLineTools

Instalar Xcode (versión completa)

Para compilaciones iOS, necesita la aplicación Xcode completa. La forma más rápida de instalarla en un servidor headless es usando xcodes:

# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Add Homebrew to PATH
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

# Install xcodes CLI tool
brew install xcodes

# List available Xcode versions
xcodes list

# Install the latest stable Xcode
xcodes install 16.2

# Set it as the active Xcode
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer

# Verify
xcodebuild -version
# Xcode 16.2
# Build version 16C5032a

Instalar simuladores iOS

# Install the iOS 18 simulator runtime
xcodebuild -downloadPlatform iOS

# Verify simulator availability
xcrun simctl list runtimes
# == Runtimes ==
# iOS 18.2 (18.2 - 22C150) - com.apple.CoreSimulator.SimRuntime.iOS-18-2

4. Paso 2: Instalar el GitHub Actions Runner

Ahora vamos a descargar y configurar el agente GitHub Actions runner. Navegue a su repositorio en GitHub, vaya a Settings > Actions > Runners > New self-hosted runner y seleccione macOS + ARM64.

Descargar y configurar

# Create a directory for the runner
mkdir -p ~/actions-runner && cd ~/actions-runner

# Download the latest runner package (ARM64)
curl -o actions-runner-osx-arm64-2.321.0.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-osx-arm64-2.321.0.tar.gz

# Extract the package
tar xzf actions-runner-osx-arm64-2.321.0.tar.gz

# Configure the runner
# Replace YOUR_TOKEN with the token from GitHub Settings
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
  --token YOUR_TOKEN \
  --name "mac-mini-m4-runner" \
  --labels "self-hosted,macOS,ARM64,M4" \
  --work "_work"

# Test the runner interactively first
./run.sh

Instalar como un servicio launchd persistente

Ejecutar el agente de forma interactiva está bien para pruebas, pero para producción necesita que se inicie automáticamente al arrancar y se reinicie si falla. GitHub proporciona un script de instalación de servicio integrado para macOS:

# Install as a launchd service
cd ~/actions-runner
sudo ./svc.sh install

# Start the service
sudo ./svc.sh start

# Check the service status
sudo ./svc.sh status
# Expected: "active (running)"

# View the launchd plist (for reference)
cat /Library/LaunchDaemons/actions.runner.*.plist

El servicio ahora se iniciará automáticamente al arrancar y se reiniciará si el proceso muere. Puede verificar que el runner aparezca como "Idle" en la página Settings > Actions > Runners de su repositorio de GitHub.

Configurar el Runner para múltiples repositorios (nivel de organización)

# For an organization-level runner, use the organization URL:
./config.sh --url https://github.com/YOUR_ORG \
  --token YOUR_ORG_TOKEN \
  --name "mac-mini-m4-org-runner" \
  --labels "self-hosted,macOS,ARM64,M4" \
  --runnergroup "Default" \
  --work "_work"

# This allows ALL repositories in your organization to use this runner

5. Paso 3: Crear su workflow de compilación iOS

Cree un archivo de workflow en su repositorio en .github/workflows/ios-build.yml. Este workflow se ejecutará en su Mac Mini M4 self-hosted runner.

name: iOS Build & Test

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: [self-hosted, macOS, ARM64, M4]

    env:
      SCHEME: "MyApp"
      DESTINATION: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2"

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Select Xcode version
        run: |
          sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
          xcodebuild -version

      - name: Resolve Swift Package Dependencies
        run: |
          xcodebuild -resolvePackageDependencies \
            -scheme "$SCHEME" \
            -clonedSourcePackagesDirPath .spm-cache

      - name: Build the app
        run: |
          xcodebuild build \
            -scheme "$SCHEME" \
            -destination "$DESTINATION" \
            -clonedSourcePackagesDirPath .spm-cache \
            -derivedDataPath DerivedData \
            | xcbeautify

      - name: Run unit tests
        run: |
          xcodebuild test \
            -scheme "$SCHEME" \
            -destination "$DESTINATION" \
            -clonedSourcePackagesDirPath .spm-cache \
            -derivedDataPath DerivedData \
            -resultBundlePath TestResults.xcresult \
            | xcbeautify

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: TestResults.xcresult

Añadir firma de código para compilaciones de release

Para despliegues en TestFlight o App Store, añada pasos de firma de código. Almacene sus certificados y perfiles de aprovisionamiento como GitHub Secrets:

  deploy:
    needs: build
    runs-on: [self-hosted, macOS, ARM64, M4]
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install certificate and provisioning profile
        env:
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          # Create a temporary keychain
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          # Import certificate
          CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
          echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
          security import $CERTIFICATE_PATH -P "$P12_PASSWORD" \
            -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          security list-keychain -d user -s $KEYCHAIN_PATH

          # Install provisioning profile
          PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
          echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles

      - name: Build for distribution
        run: |
          xcodebuild archive \
            -scheme "MyApp" \
            -archivePath DerivedData/MyApp.xcarchive \
            -destination "generic/platform=iOS" \
            CODE_SIGN_STYLE=Manual

      - name: Export IPA
        run: |
          xcodebuild -exportArchive \
            -archivePath DerivedData/MyApp.xcarchive \
            -exportOptionsPlist ExportOptions.plist \
            -exportPath DerivedData/Export

      - name: Upload to TestFlight
        env:
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
        run: |
          xcrun altool --upload-app \
            -f DerivedData/Export/MyApp.ipa \
            -t ios \
            --apiKey $APP_STORE_CONNECT_API_KEY

6. Paso 4: Optimizar el rendimiento

Una de las mayores ventajas de un self-hosted runner es la caché persistente. Aquí tiene las optimizaciones clave para sacar el máximo provecho de su Mac Mini M4.

Habilitar caché de DerivedData

Como el runner es persistente, DerivedData se preserva entre compilaciones. Use una ruta de DerivedData consistente:

# In your workflow, always use:
-derivedDataPath DerivedData

# On the runner, periodically clean old DerivedData to save space:
# Add a cron job to clean builds older than 7 days
echo "0 3 * * 0 find ~/actions-runner/_work/*/DerivedData -maxdepth 0 -mtime +7 -exec rm -rf {} +" \
  | crontab -

Cachear paquetes SPM

# Use clonedSourcePackagesDirPath to keep SPM packages on disk
xcodebuild build \
  -scheme "MyApp" \
  -clonedSourcePackagesDirPath ~/spm-cache \
  -derivedDataPath DerivedData

# This avoids re-downloading packages on every build

Ejecución paralela de pruebas

# Run tests in parallel across multiple simulators
xcodebuild test \
  -scheme "MyApp" \
  -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
  -destination "platform=iOS Simulator,name=iPhone 15,OS=17.5" \
  -parallel-testing-enabled YES \
  -maximum-parallel-testing-workers 4 \
  -derivedDataPath DerivedData \
  | xcbeautify

Instalar xcbeautify para mejores logs

# xcbeautify formats Xcode output for CI environments
brew install xcbeautify

# Use it by piping xcodebuild output:
xcodebuild build -scheme "MyApp" | xcbeautify

7. Solución de problemas comunes

El Runner aparece como "Offline" en GitHub

Esto generalmente significa que el servicio launchd no está ejecutándose. Verifique el estado del servicio y los logs:

# Check service status
sudo ./svc.sh status

# View logs
cat ~/actions-runner/_diag/Runner_*.log | tail -50

# Restart the service
sudo ./svc.sh stop
sudo ./svc.sh start

La firma de código falla con "No signing certificate"

El servicio launchd se ejecuta bajo un contexto de usuario diferente. Asegúrese de que el keychain sea accesible:

# Ensure the login keychain is unlocked for the runner user
security unlock-keychain -p "YOUR_PASSWORD" ~/Library/Keychains/login.keychain-db

# Or use a dedicated keychain in your workflow (recommended)
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain

El simulador no arranca

Los simuladores a veces se atascan. Reinícielos entre compilaciones:

# Shutdown all running simulators
xcrun simctl shutdown all

# Erase all simulator data (nuclear option)
xcrun simctl erase all

# Boot a specific simulator
xcrun simctl boot "iPhone 16 Pro"

Espacio en disco insuficiente

Las compilaciones de Xcode generan muchos datos. Configure una limpieza automática:

# Clean old DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData/*

# Remove old simulator runtimes
xcrun simctl runtime delete all

# Clean Homebrew cache
brew cleanup --prune=7

# Remove old Xcode archives
rm -rf ~/Library/Developer/Xcode/Archives/*

8. Análisis de costes

Aquí tiene una comparación detallada de costes para diferentes tamaños de equipo y volúmenes de compilación:

Tamaño del equipo Compilaciones/Mes Coste alojado en GitHub Coste MyRemoteMac Ahorro mensual
Desarrollador individual 100 compilaciones (10 min promedio) $80/mes $75/mes $5/mes
Equipo pequeño (5) 500 compilaciones (10 min promedio) $400/mes $75/mes $325/mes
Equipo mediano (15) 1500 compilaciones (10 min promedio) $1,200/mes $179/mes (M4 Pro) $1,021/mes
Empresa (50+) 5000+ compilaciones $4,000+/mes $358/mes (2x M4 Pro) $3,642+/mes

Conclusión: Para equipos que ejecutan más de ~100 compilaciones por mes, un Mac Mini M4 self-hosted se amortiza de inmediato. Y como las compilaciones son más rápidas en hardware persistente con cachés precargadas, su equipo también ahorra tiempo de desarrollo.

Guías relacionadas

¿Listo para acelerar sus compilaciones iOS?

Despliegue un Mac Mini M4 dedicado como su GitHub Actions runner. Desde $75/mes con una prueba gratuita de 7 días.