Cómo ejecutar un runner self-hosted de CircleCI en un Mac
Machine runner 3 de CircleCI se instala en macOS desde el tap de Homebrew de CircleCI y se ejecuta como LaunchAgent. Crea un namespace y una resource class, copia el token, ponlo en el config.yaml del runner y arranca el servicio. Los jobs llegan a él con machine: true y resource_class: namespace/name.
Lo que necesitas antes
- Permisos de administrador de la organización en CircleCI. Un administrador tiene que aceptar las condiciones de los runners en Org, luego Runners, antes de que aparezca el menú.
- Al menos un crédito en la cuenta. CircleCI dice que los jobs en runners no gastan créditos, pero el almacenamiento y la transferencia de red sí pueden.
- Un Mac con Apple silicon, acceso de administrador, Homebrew y Xcode.
sha256sum, que CircleCI pide como requisito. Lo consigues conbrew install coreutils.
1. Crea un namespace y una resource class
En la aplicación web, abre Runners y selecciona Create Resource Class. Cada organización tiene un namespace. Si publicas orbs, ya lo tienes. Ponle a la resource class un nombre como mac-mini-m6. Guarda y copia el token. CircleCI solo lo muestra una vez.
La CLI hace lo mismo:
circleci namespace create <name> --org-id <your-organization-id> circleci runner resource-class create <namespace>/<resource-class> <description> --generate-token
2. Instala el runner con Homebrew
brew tap circleci-public/circleci brew trust circleci-public/circleci brew install circleci-runner
La línea del medio es nueva. Homebrew 7.0.7, la versión de nuestro Mac en octubre de 2026, se niega a cargar paquetes de un tap de terceros hasta que confías en él. La página de CircleCI aún no muestra este paso. El runner llega como cask de Homebrew.
Puede que macOS muestre un aviso de que se ha añadido un elemento en segundo plano de Circle Internet Services. Es normal. Homebrew también escribe el plist del LaunchAgent en ~/Library/LaunchAgents/com.circleci.runner.plist.
3. Añade el token a config.yaml
nano $HOME/Library/Preferences/com.circleci.runner/config.yaml
runner: name: "mac-mini-m6" working_directory: "/Users/$USER/Library/com.circleci.runner/workdir" cleanup_working_directory: true api: auth_token: "your-resource-class-token"
Con cleanup_working_directory activado, cada job empieza desde un checkout limpio. El DerivedData de Xcode vive por defecto en ~/Library/Developer/Xcode/DerivedData, así que las cachés de build se conservan igualmente. Si pasas -derivedDataPath dentro del directorio de trabajo, la limpieza lo borra tras cada job.
Protege el token
El token de la resource class es lo que permite a una máquina reclamar jobs de esa clase. Quien lo lea puede conectar su propia máquina y recibir tus jobs, con tus secrets. Restringe el archivo de configuración a tu usuario y rota el token si alguna vez se filtra.
chmod 600 $HOME/Library/Preferences/com.circleci.runner/config.yaml ls -l $HOME/Library/Preferences/com.circleci.runner/config.yaml
Con los jobs pasa lo mismo. Se ejecutan con el usuario de macOS que arrancó el runner, en el mismo disco que todo lo demás. Apunta a esta resource class solo proyectos de confianza.
4. Acepta la notarización
El binario viene de internet, así que macOS tiene que aprobarlo. CircleCI indica que primero compruebes la firma y luego quites el atributo de cuarentena.
spctl -a -vvv -t install "$(brew --prefix)/bin/circleci-runner" sudo xattr -r -d com.apple.quarantine "$(brew --prefix)/bin/circleci-runner"
El primer comando debería decir accepted, con source Notarized Developer ID.
5. Arranca el runner en el dominio GUI
Ejecuta esto desde Terminal en el escritorio del Mac. El dominio GUI es la sesión de escritorio iniciada. Ahí viven el simulador y el llavero de inicio de sesión.
launchctl bootstrap gui/$(id -u) $HOME/Library/LaunchAgents/com.circleci.runner.plist launchctl enable gui/$(id -u)/com.circleci.runner launchctl kickstart -k gui/$(id -u)/com.circleci.runner launchctl print gui/$(id -u)/com.circleci.runner
CircleCI también documenta una opción de dominio de usuario para sesiones sin pantalla. Mueve el plist a /Library/LaunchAgents. En macOS 27 vimos que un plist en esa carpeta rompía el inicio de sesión automático en cada arranque. Consulta inicio de sesión automático en un Mac sin pantalla. Para trabajo de iOS, te recomendamos el dominio GUI con inicio de sesión automático.
6. Haz que sobreviva a un reinicio
Un plist en ~/Library/LaunchAgents se carga cuando su usuario inicia sesión. Activa el inicio de sesión automático para esta cuenta en Ajustes del Sistema, en Usuarios y grupos. Desactiva el reposo con sudo pmset -a sleep 0. Reinicia una vez y vuelve a comprobar launchctl print. Los logs están en ~/Library/Logs/com.circleci.runner/runner.log.
7. Apunta un job al Mac
version: 2.1
jobs:
ios-tests:
machine: true
resource_class: your-namespace/mac-mini-m6
steps:
- checkout
- run:
name: Run tests
command: |
rm -rf build/TestResults.xcresult
xcodebuild test -project MyApp.xcodeproj -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5' -resultBundlePath build/TestResults.xcresult
- run:
name: Zip result bundle
when: always
command: ditto -c -k --keepParent build/TestResults.xcresult TestResults.xcresult.zip
- store_artifacts:
path: TestResults.xcresult.zip
workflows:
ios:
jobs:
- ios-testsLa documentación de CircleCI indica dos campos que debe tener un job de runner: machine: true y la resource_class. Aquí no hay clave macos: xcode:, porque no eliges una imagen. El job usa el Xcode que tenga seleccionado el Mac. En un Mac de MacRun es Xcode 26.6 con el runtime del simulador de iOS 26.5.
Errores comunes y soluciones
Refusing to load cask ... from untrusted tap. Ejecutabrew trust circleci-public/circleciy vuelve a instalar.- macOS bloquea el binario. Te saltaste el paso de notarización. Ejecuta el comando
xattrde arriba. - Los jobs se quedan en cola y nunca empiezan. Comprueba que la resource class de config.yml coincide con la que creaste, y luego lee
runner.log. - La caché de capas de Docker no funciona. CircleCI indica que no está soportada en runners self-hosted.
xcodebuild: error: Existing file at -resultBundlePath. Borra el bundle anterior antes del paso de tests. Nos pasó con Xcode 26.6.
Para parar el runner, usa launchctl bootout gui/$(id -u)/com.circleci.runner. Para quitarlo, ejecuta brew uninstall --cask circleci-public/homebrew-circleci/circleci-runner.
Por qué ayuda un Mac dedicado
La gran ventaja son las cachés en caliente. En nuestro benchmark compilamos la app de Wikipedia para iOS con Xcode 26.6. Un job típico tras un cambio pequeño tardó 27 segundos en un M6 en caliente. Tardó 269 segundos en un runner macos-26 recién creado de GitHub. Un build limpio tardó 86 segundos frente a 183.
Cuándo basta con el macOS alojado de CircleCI
CircleCI tiene sus propios executors de Mac. Su documentación, leída en octubre de 2026, incluye m4pro.medium con 6 vCPU y 28 GB, y m4pro.large con 12 vCPU y 56 GB. Los dos tienen más memoria que nuestro M6 de 16 GB. Si tu suite necesita tanta memoria, o compilas unas pocas veces por semana, quédate en lo alojado. Tampoco elijas MacRun si necesitas un SLA, una IP fija o varias regiones.
Consulta otros sistemas de CI para la parte de MacRun, o compara costes con tus propios minutos.
Preguntas frecuentes
¿El runner self-hosted de CircleCI es gratis?
+
CircleCI dice que la ejecución en runners no gasta créditos. Necesitas al menos un crédito en la cuenta, porque el almacenamiento y la transferencia de red se pueden seguir cobrando.
¿Dónde está la configuración del runner de CircleCI en macOS?
+
En $HOME/Library/Preferences/com.circleci.runner/config.yaml. Los logs van a $HOME/Library/Logs/com.circleci.runner/runner.log.
¿Uso el dominio GUI o el dominio de usuario?
+
Para builds de iOS, el dominio GUI con inicio de sesión automático. Se ejecuta dentro de la sesión de escritorio que necesitan el simulador y el llavero de inicio de sesión.
¿Funciona la caché de capas de Docker en un runner self-hosted de CircleCI?
+
No. CircleCI indica que la caché de capas de Docker no está soportada en runners self-hosted.