1. ¿Por qué Self-Hosted GitLab Runners en Mac?
GitLab ofrece runners compartidos en Linux, pero compilar apps iOS requiere macOS ejecutándose en hardware Apple. Los runners compartidos macOS de GitLab son limitados y costosos. Un Mac Mini M4 self-hosted runner le ofrece:
El plan gratuito de GitLab incluye 400 minutos CI/CD en runners compartidos. En self-hosted, no hay límite.
Compile en el mismo chip M4 que ejecutan los dispositivos de sus usuarios. Sin sobrecarga de traducción Rosetta.
Instale cualquier versión de Xcode, simuladores, herramientas y dependencias que necesite.
Las cachés de DerivedData, paquetes SPM y CocoaPods sobreviven entre ejecuciones del pipeline.
2. Instalar GitLab Runner
Conéctese por SSH a su Mac Mini M4 e instale el GitLab Runner usando Homebrew:
# Connect to your Mac Mini M4
ssh admin@your-server-ip
# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# Install GitLab Runner
brew install gitlab-runner
# Verify installation
gitlab-runner --version
# Version: 17.7.0
# Git revision: ...
# Git branch: 17-7-stable
# GO version: go1.22.10
# Built: ...
# OS/Arch: darwin/arm64
Instalar como un servicio macOS
# Install the runner as a launchd service
# This ensures it starts automatically on boot
brew services start gitlab-runner
# Verify the service is running
brew services list | grep gitlab-runner
# gitlab-runner started admin ~/Library/LaunchAgents/homebrew.mxcl.gitlab-runner.plist
# Check runner status
gitlab-runner status
# gitlab-runner: Service is running
3. Registrar el Runner
Vaya a su proyecto (o grupo) de GitLab y navegue a Settings > CI/CD > Runners > New project runner. Copie el token de registro.
Registrar con el nuevo flujo de registro de Runner (GitLab 16+)
# Register the runner using the authentication token from GitLab UI
# (GitLab 16+ uses authentication tokens instead of registration tokens)
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--token "YOUR_RUNNER_AUTHENTICATION_TOKEN" \
--executor "shell" \
--description "mac-mini-m4-runner" \
--tag-list "macos,apple-silicon,m4,ios,xcode"
Registrar con token de registro heredado (GitLab 15 y anterior)
# For older GitLab instances using registration tokens
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "YOUR_REGISTRATION_TOKEN" \
--executor "shell" \
--description "mac-mini-m4-runner" \
--tag-list "macos,apple-silicon,m4,ios,xcode" \
--run-untagged="false"
Verificar la configuración del Runner
# View the runner config file
cat ~/.gitlab-runner/config.toml
# Expected output:
# concurrent = 2
# check_interval = 0
#
# [session_server]
# session_timeout = 1800
#
# [[runners]]
# name = "mac-mini-m4-runner"
# url = "https://gitlab.com/"
# token = "..."
# executor = "shell"
# [runners.cache]
# MaxUploadedArchiveSize = 0
# Adjust concurrency based on your hardware:
# Mac Mini M4 (16GB): concurrent = 2
# Mac Mini M4 Pro (24GB): concurrent = 3
# Mac Mini M4 Pro (48GB): concurrent = 4
Edite ~/.gitlab-runner/config.toml para ajustar la concurrencia:
# Edit the config
nano ~/.gitlab-runner/config.toml
# Set concurrent to match your hardware capacity
concurrent = 2
# Restart the runner to apply changes
gitlab-runner restart
El runner debería aparecer ahora como Online en su proyecto de GitLab bajo Settings > CI/CD > Runners.
4. Crear .gitlab-ci.yml para iOS
Cree un archivo .gitlab-ci.yml en la raíz de su repositorio. Este pipeline completo compila, prueba y despliega su app iOS:
# .gitlab-ci.yml
stages:
- setup
- build
- test
- deploy
variables:
SCHEME: "MyApp"
WORKSPACE: "MyApp.xcworkspace"
DESTINATION: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2"
DERIVED_DATA: "${CI_PROJECT_DIR}/DerivedData"
# Only run on our Mac runner
default:
tags:
- macos
- m4
# ---- SETUP ----
setup:
stage: setup
script:
- sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
- xcodebuild -version
- swift --version
# Install CocoaPods if using Podfile
- |
if [ -f "Podfile" ]; then
pod install --repo-update
fi
cache:
key: pods-${CI_COMMIT_REF_SLUG}
paths:
- Pods/
- .spm-cache/
# ---- BUILD ----
build:
stage: build
needs: ["setup"]
script:
- |
xcodebuild build \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-clonedSourcePackagesDirPath ".spm-cache" \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify
cache:
key: derived-data-${CI_COMMIT_REF_SLUG}
paths:
- DerivedData/
- .spm-cache/
artifacts:
paths:
- DerivedData/
expire_in: 1 hour
# ---- TEST ----
unit_tests:
stage: test
needs: ["build"]
script:
- |
xcodebuild test \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-resultBundlePath "TestResults.xcresult" \
-parallel-testing-enabled YES \
| xcbeautify
artifacts:
when: always
paths:
- TestResults.xcresult/
reports:
junit: TestResults.xcresult/report.junit
expire_in: 7 days
after_script:
- xcrun simctl shutdown all 2>/dev/null || true
# ---- DEPLOY ----
deploy_testflight:
stage: deploy
needs: ["unit_tests"]
only:
- main
script:
- |
# Install or update Fastlane
which fastlane || brew install fastlane
# Run Fastlane beta lane
fastlane beta
environment:
name: testflight
variables:
MATCH_PASSWORD: ${MATCH_PASSWORD}
APP_STORE_CONNECT_API_KEY_ID: ${APP_STORE_KEY_ID}
APP_STORE_CONNECT_API_ISSUER_ID: ${APP_STORE_ISSUER_ID}
APP_STORE_CONNECT_API_KEY_CONTENT: ${APP_STORE_KEY_CONTENT}
Añadir variables CI/CD en GitLab
Navegue a Settings > CI/CD > Variables en su proyecto de GitLab y añada estas variables como "Masked" y "Protected":
-
MATCH_PASSWORD- Contraseña para el cifrado de Fastlane Match -
APP_STORE_KEY_ID- ID de clave API de App Store Connect -
APP_STORE_ISSUER_ID- ID de emisor de App Store Connect -
APP_STORE_KEY_CONTENT- Contenido del archivo de clave .p8
5. Optimizar el rendimiento
Configuración de caché de GitLab
GitLab Runner admite caché local para ejecutores shell. Como su runner es persistente, las cachés locales son extremadamente eficientes:
# In .gitlab-ci.yml, configure cache per branch:
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- DerivedData/
- .spm-cache/
- Pods/
policy: pull-push
# For test jobs that don't modify cache, use pull-only:
unit_tests:
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- DerivedData/
policy: pull
Usar artefactos para datos entre etapas
# Pass build artifacts between stages efficiently
build:
artifacts:
paths:
- DerivedData/Build/Products/
expire_in: 2 hours
# The test stage receives the built products without rebuilding
test:
needs: ["build"] # only download artifacts from the build job
script:
- xcodebuild test-without-building \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}"
Ejecución paralela de pruebas
# Split tests across parallel jobs using GitLab's parallel keyword
unit_tests:
stage: test
parallel: 2
script:
- |
# Use test plan partitioning or custom splitting
xcodebuild test \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 4
Limpieza programada de caché
# On the Mac Mini, set up a weekly cleanup cron job
crontab -e
# Add these lines:
# Clean DerivedData older than 7 days every Sunday at 3 AM
0 3 * * 0 find ~/builds/*/DerivedData -maxdepth 0 -mtime +7 -exec rm -rf {} + 2>/dev/null
# Clean old GitLab Runner builds older than 14 days
0 4 * * 0 find ~/builds -maxdepth 2 -mtime +14 -type d -exec rm -rf {} + 2>/dev/null
# Clean Homebrew cache monthly
0 5 1 * * /opt/homebrew/bin/brew cleanup --prune=30 2>/dev/null
6. Solución de problemas
El Runner aparece "offline" en GitLab
Verifique el estado del servicio del runner y los logs:
# Check service status
brew services list | grep gitlab-runner
# View logs
cat /usr/local/var/log/gitlab-runner.log
# Restart the service
brew services restart gitlab-runner
# Verify connectivity to GitLab
gitlab-runner verify
Errores de permisos denegados durante las compilaciones
El runner puede necesitar acceso al directorio de desarrollador de Xcode:
# Ensure the runner user has Xcode access
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
sudo xcodebuild -license accept
# If using simulators, ensure the user can access them
xcrun simctl list devices
La caché no se restaura
Asegúrese de que las claves de caché sean consistentes y que las rutas existan:
# Check cache directory permissions
ls -la ~/builds/
# The shell executor stores caches locally by default
# Verify the cache directory in config.toml:
cat ~/.gitlab-runner/config.toml
# Ensure [runners.cache] section has the right settings
# For local caching (most efficient for persistent runners):
# [runners.cache]
# Type = "" # empty = local cache
La compilación de Xcode se cuelga o agota el tiempo
Esto suele ser causado por solicitudes de acceso al keychain o problemas con el simulador:
# Unlock the keychain before builds
security unlock-keychain -p "YOUR_PASSWORD" ~/Library/Keychains/login.keychain-db
# Kill stuck simulators
xcrun simctl shutdown all
pkill -f "Simulator.app" 2>/dev/null || true
# Set a build timeout in .gitlab-ci.yml
build:
timeout: 30 minutes
7. Preguntas frecuentes
¿Puedo usar un ejecutor Docker en macOS?
Docker en macOS ejecuta contenedores Linux en una VM, que no puede acceder a APIs de macOS, Xcode ni simuladores iOS. Para compilaciones iOS, debe usar el ejecutor shell. Docker es adecuado para Swift del lado del servidor u otras tareas basadas en Linux ejecutándose junto a su Mac runner.
¿Cómo registro el runner para un grupo de GitLab?
Vaya a Settings > CI/CD > Runners > New group runner de su grupo de GitLab. Use el token del runner de grupo en lugar del token de proyecto. Esto hace que el runner esté disponible para todos los proyectos dentro del grupo.
¿Debo usar el ejecutor shell o el ejecutor SSH?
Use el ejecutor shell. Ejecuta comandos directamente en el Mac, lo que da acceso completo a Xcode, simuladores y el keychain. El ejecutor SSH es para máquinas remotas, lo cual es innecesario cuando el runner ya está en el Mac.
¿Puedo ejecutar tanto GitLab como GitHub Actions runners en el mismo Mac?
Sí. Ambos runners son ligeros y pueden coexistir en el mismo Mac Mini M4. Solo asegúrese de tener en cuenta el uso combinado de recursos al establecer los niveles de concurrencia de cada runner.
¿Cómo actualizo el GitLab Runner?
# Update via Homebrew
brew upgrade gitlab-runner
# Restart the service
brew services restart gitlab-runner
# Verify the new version
gitlab-runner --version