Доставка 1–5 мин

Без очереди Runner — сразу к iOS-сборке

$21.8 / день · физическая машина
Настроить облачный Mac
M4 · 16 GB Фиксированная версия Xcode Пять глобальных узлов

CI/CD iOS без Mac: self-hosted Runner GitHub Actions на облачном Mac

Hosted macOS Runner в пиковые часы — очередь до 20 минут, версия Xcode не под вашим контролем. В статье — self-hosted Runner на Mac mini M4: Archive, подпись, fastlane → TestFlight и сравнение с Jenkins Agent.

Почему self-hosted Runner лучше для iOS-сборки, чем hosted Runner GitHub?

Hosted macOS Runner в GitHub Actions теоретически избавляет от обслуживания железа, но в продакшене есть несколько неизбежных проблем.

Первое — ожидание в очереди. Пул macOS Runner меньше Linux; в пик (UTC 12–20) от runs-on: macos-latest до готовности — медиана 8–15 мин, иногда 20+. Clean Build 6 мин — очередь длиннее самой сборки.

Второе — неконтролируемая версия Xcode. macos-latest тихо меняет Xcode; были апгрейды 15→16 без уведомления — нужен xcode-select и проверка совместимости.

Третье — быстрый расход бесплатного лимита. macOS Runner в 10 раз дороже Linux (документация GitHub); 2000 минут/мес ≈ 200 минут macOS — интенсивный CI быстро уходит в перерасход.

Self-hosted Runner на своём Mac снимает эти три проблемы: Runner всегда онлайн, версию Xcode контролируете вы, время выполнения не тарифицируется. Нужна постоянно работающая macOS-машина — это и решает статья.

8–20 min
Очередь hosted macOS Runner в пик
< 30 s
Self-hosted Runner (онлайн Mac) — задержка старта job
10×
Множитель тарифа macOS vs Linux (GitHub)
Фиксированная
Версия Xcode на self-hosted узле полностью под вашим контролем

Подготовка: активация облачного Mac-узла и базовая среда

До iOS CI/CD нужен macOS с SSH и 24/7. Пример — выделенный Mac mini M4 VPSRox (или свой Mac mini/Studio); после доставки в консоли шаги ниже — за 5 минут.

SSH-доступ и Xcode Command Line Tools

  1. 01
    SSH-ключи

    Скопируйте публичный IP в консоли и выполните локально:

    ssh-copy-id -i ~/.ssh/id_ed25519.pub user@<IP-узла>

    Затем отключите вход по паролю (PasswordAuthentication no), чтобы защититься от brute force.

  2. 02
    Xcode Command Line Tools

    xcode-select --install

    Для полного Xcode GUI скачайте .xip с developer.apple.com/downloads, в /Applications/ и переключите путь:

    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

  3. 03
    Установить Homebrew

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

    После установки добавьте /opt/homebrew/bin в PATH, пропишите в ~/.zprofile.

  4. 04
    Проверить цепочку сборки Xcode

    xcodebuild -version

    Убедитесь, что выводятся версия Xcode и Build Version — в YAML можно зафиксировать версию переменной окружения XCODE_VERSION.

Рекомендация: xcodes для нескольких версий Xcode

brew install xcodesorg/made/xcodes — быстрая смена Xcode. В CI: xcodes select 16.2 для фиксации версии.

Установка и регистрация self-hosted Runner GitHub Actions

Пакет self-hosted Runner от GitHub запускает на Mac polling-демон: при срабатывании workflow job выполняется локально; открывать порты наружу не нужно.

Получите registration token в репозитории GitHub

Репозиторий → SettingsActionsRunnersNew self-hosted runner, macOS, скопируйте команды и token (1 час).

  1. 01
    Скачать и распаковать Runner

    mkdir actions-runner && cd actions-runner

    curl -o actions-runner-osx-arm64-2.319.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-osx-arm64-2.319.1.tar.gz

    tar xzf ./actions-runner-osx-arm64-2.319.1.tar.gz

    Выберите пакет osx-arm64 (Apple Silicon); версию смотрите на странице GitHub.

  2. 02
    Зарегистрировать Runner

    ./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token <TOKEN> --name mac-m4-vpsrox --labels mac,xcode16,ios

    Пользовательские метки --labels позже указываются в runs-on YAML для точной маршрутизации на эту машину.

  3. 03
    Служба launchd (автозапуск)

    ./svc.sh install

    ./svc.sh start

    launchd поднимет процесс Runner после входа пользователя. Статус: ./svc.sh status.

Указать self-hosted Runner в Workflow YAML

После регистрации в .github/workflows/ios-ci.yml замените runs-on на ваши метки:

jobs:
  build:
    runs-on: [self-hosted, mac, xcode16]
    steps:
      - uses: actions/checkout@v4
      - name: Show Xcode version
        run: xcodebuild -version
Безопасность: осторожно с self-hosted Runner в public repo

Self-hosted Runner в public repo может выполнять fork PR на вашем Mac. Рекомендации: ① private/org; ② не admin account; ③ аудит ~/.bash_history и ~/actions-runner/_diag/.

Настройка среды сборки Xcode и управление зависимостями

Чистая iOS-сборка обычно требует: зависимости, кэш компиляции, изоляцию Derived Data.

CocoaPods vs Swift Package Manager

Если проект использует CocoaPods, достаточно один раз установить на машине Runner:

sudo gem install cocoapods

В шаге CI:

- name: Install CocoaPods
  run: pod install --repo-update
  working-directory: ./ios

При Swift Package Manager Xcode разрешает зависимости при первой сборке. Для ускорения CI кэшируйте результат разрешения SPM в job:

- uses: actions/cache@v4
  with:
    path: ~/Library/Developer/Xcode/DerivedData
    key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}

Изоляция пути Derived Data

При параллельных сборках нескольких проектов на одном Runner общий DerivedData мешает. Явно задайте путь в xcodebuild:

xcodebuild \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -sdk iphoneos \
  -configuration Release \
  -derivedDataPath ./DerivedData \
  clean build

Настройка параллельных сборок

Mac mini M4 — 10 ядер CPU (4 performance + 6 efficiency); Xcode по умолчанию ставит параллелизм по числу ядер. Для крупных проектов можно снизить, чтобы не перегружать память:

defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 6

SwiftUI-проект из 80 Swift-файлов на M4 с 16 GB RAM: Clean Build ~4 мин 12 с, без нагрузки на swap.

Подпись кода и Keychain: полный цикл Archive

Подпись кода — самый частый источник ошибок в iOS CI/CD. В CI без GUI поведение macOS Keychain отличается от обычной среды разработки; Keychain нужно явно управлять.

Сравнение двух основных схем подписи

Вариант Сценарии применения Плюсы Минусы
Ручной импорт p12-сертификата CI на одной машине для малой команды Простая настройка, без внешних зависимостей Ротация сертификатов — ручной re-import
fastlane match Командная работа / несколько CI-узлов Единое шифрованное хранение сертификатов, автоматическая ротация Нужен Git-репозиторий или S3 backend

Ручная схема p12: создание временного Keychain

В CI надёжнее создавать временный Keychain на каждый job и удалять после сборки:

# Create a temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain

# Import signing certificate
security import "$CERTIFICATE_P12_PATH" \
  -k build.keychain \
  -P "$P12_PASSWORD" \
  -T /usr/bin/codesign \
  -T /usr/bin/xcodebuild

# Allow xcodebuild to access the key without UI prompt
security set-key-partition-list \
  -S apple-tool:,apple: \
  -s -k "$KEYCHAIN_PASSWORD" build.keychain

KEYCHAIN_PASSWORD, P12_PASSWORD и др. — только в GitHub Repository Secrets через ${{ secrets.KEYCHAIN_PASSWORD }}, не в YAML.

Выполнить Archive и экспорт IPA

Archive → Export в два шага: xcodebuild archive для .xcarchive, затем -exportArchive для IPA.

# Step 1: Archive
xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -sdk iphoneos \
  -archivePath ./build/MyApp.xcarchive \
  CODE_SIGN_IDENTITY="Apple Distribution: Your Name (XXXXXXXX)" \
  DEVELOPMENT_TEAM="XXXXXXXX"

# Step 2: Export IPA
xcodebuild -exportArchive \
  -archivePath ./build/MyApp.xcarchive \
  -exportOptionsPlist ExportOptions.plist \
  -exportPath ./build/

ExportOptions.plist: method (app-store/ad-hoc), teamID, signingStyle. Шаблон — после ручного Export в Xcode.

Подключение fastlane для автозагрузки в TestFlight

После IPA — загрузка в TestFlight через fastlane pilot / upload_to_testflight с App Store Connect API Key, без пароля Apple ID.

Установка fastlane

Рекомендуется Bundler для версии fastlane и избежания конфликтов глобальной установки:

# In project root, create Gemfile
source "https://rubygems.org"
gem "fastlane"

Затем bundle install; fastlane только через bundle exec fastlane ....

Настройка App Store Connect API Key

App Store Connect → Users and AccessIntegrations → API Key, скачайте .p8, Key ID и Issuer ID.

Задайте в GitHub Secrets следующие переменные:

  • ASC_API_KEY_ID: ID API Key
  • ASC_API_ISSUER_ID:Issuer ID
  • ASC_API_KEY_CONTENT.p8 Содержимое файла в кодировке Base64

Пример конфигурации Fastfile

lane :beta do
  api_key = app_store_connect_api_key(
    key_id: ENV["ASC_API_KEY_ID"],
    issuer_id: ENV["ASC_API_ISSUER_ID"],
    key_content: Base64.decode64(ENV["ASC_API_KEY_CONTENT"])
  )

  upload_to_testflight(
    api_key: api_key,
    ipa: "./build/MyApp.ipa",
    skip_waiting_for_build_processing: true
  )
end

Вызов в YAML GitHub Actions:

- name: Upload to TestFlight
  env:
    ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }}
    ASC_API_ISSUER_ID: ${{ secrets.ASC_API_ISSUER_ID }}
    ASC_API_KEY_CONTENT: ${{ secrets.ASC_API_KEY_CONTENT }}
  run: bundle exec fastlane beta
Ускорение: не ждать обработку App Store

skip_waiting_for_build_processing: true — fastlane завершается после upload, без ожидания обработки IPA в App Store Connect (10–30 мин). Если следующий шаг не ждёт результат — заметно короче CI.

Типичные ошибки Keychain: централизованная диагностика

Сообщения об ошибках подписи кода часто неясны. Ниже — самые частые в CI без GUI, их причины и решения.

Сообщение об ошибке Причина Решение
errSecItemNotFound Сертификат не импортирован в активный Keychain или временный Keychain уже удалён GC Проверьте security default-keychain -s build.keychain; перед archive добавьте security unlock-keychain
could not find signing certificate for "Apple Distribution" Имя Identity сертификата не совпадает с CODE_SIGN_IDENTITY в проекте Xcode Выполните security find-identity -v -p codesigning build.keychain, скопируйте полную строку Identity
User interaction is not allowed Keychain заблокирован, codesign требует UI-диалог авторизации (в headless-среде невозможен) Добавьте security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain — codesign без UI
Provisioning profile doesn't include the entitlement Provisioning profile не совпадает с Entitlements проекта (Push Notifications, App Groups и т.д.) Пересоздайте provisioning profile в Apple Developer, включите все Capability, скачайте и установите заново
No signing certificate "iOS Distribution" found Provisioning profile не совпадает с типом сертификата (Distribution vs Development) В ExportOptions.plist проверьте method (app-store требует Distribution-сертификат)

Отладка: полная команда подписи в логах сборки

После xcodebuild| xcpretty -r json-compilation-database или сырой лог с CodeSign — больше деталей, чем в GUI Xcode.

Ещё одна распространённая команда отладки:

codesign -dv --verbose=4 ./build/MyApp.xcarchive/Products/Applications/MyApp.app

Проверьте встроенную подпись в .app — полна ли цепочка сертификатов.

Jenkins Agent: сравнение, миграция и компромиссы

Если у команды уже есть Jenkins или политика безопасности не позволяет пушить код в GitHub, Jenkins on Mac — распространённая альтернатива. Ключевые различия — по нескольким критериям.

Критерий Self-hosted Runner GitHub Actions Jenkins Agent (macOS)
Порог настройки Низкий, регистрация за 5 минут Средний: сначала нужен Jenkins master (обычно Linux/Docker)
Интеграция с репозиторием Нативная интеграция GitHub, статус PR-проверок пишется автоматически Нужен плагин GitHub Branch Source
Смешанные платформы В одном workflow можно смешивать macOS / Linux / Windows Нужна маршрутизация по Label, настройка сложнее
Визуализация pipeline Нативный UI GitHub Actions, наглядно Плагин Blue Ocean хорош, но требует обслуживания
Управление секретами GitHub Secrets + OIDC Jenkins Credentials, интеграция с Vault
Офлайн/внутренняя сборка Не поддерживается (нужен доступ к GitHub) Поддерживается, подходит для строгого compliance
Требования к железу Нужна онлайн-машина macOS (хост Runner) Нужна онлайн-машина macOS (хост Jenkins Agent)

Миграция с Jenkins: sh 'xcodebuild ...'run: в YAML. Различия — триггеры и concurrency: when { branch ... }on: push: branches:; lockable-resources → concurrency:.

Общее условие обеих схем: постоянно онлайн физический Mac с фиксированной версией macOS как Agent/Runner. Стабильность CI зависит от этой машины — перегрев или дрейф версий ломают сборки.

Реальная проблема многих команд — не выбор схемы, а нет Mac для постоянного CI. MacBook в офисе часто 8 GB RAM, throttling при сборке; новый Mac mini — закупка, площадь, электричество.

Выделенный облачный Mac: фиксированная сборка без своего железа

В такой ситуации аренда выделенного облачного Mac — вариант, который стоит оценить: без начальных вложений в железо, но с выделенным macOS-узлом сборки с фиксированной средой.

Пример узла VPSRox Mac mini M4: 16 GB unified memory + 256 GB NVMe SSD, 1 Gbps выделенный канал, от $21.8/день, доставка 1–5 минут, без контракта.

С точки зрения CI/CD это аренда всегда онлайн Mac mini с выделенной мощностью — возможности те же, что у локальной физической машины:

  • Любая версия Xcode (xcodes), полностью фиксированная среда сборки
  • GitHub Actions self-hosted Runner или Jenkins Agent — шаги статьи применимы напрямую
  • 10 ядер M4 и 38 TOPS Neural Engine — лучше для проектов с Core ML
  • Thunderbolt 5: кластер Mac mini для крупных команд

По сравнению с hosted Runner GitHub: при частых сборках (30+ в день) аренда по дням часто дешевле macOS Runner; по сравнению с покупкой Mac mini — без CAPEX, гибкий срок (больше машин на спринт, меньше после релиза).

Пять глобальных узлов (Сингапур, Токио, Сеул, Гонконг, восток США) — выберите ближайший к команде, чтобы снизить задержку git fetch и загрузки зависимостей.

Один Mac: CI/CD + удалённый рабочий стол

Облачный Mac не только для CI: удалённый рабочий стол (VNC/SSH), Xcode, Instruments, симулятор без локального Mac — выше утилизация узла и ниже стоимость дня.

Выделенный физический сервер · доставка за 1–5 мин

Запустить этот CI/CD pipeline

Все шаги проверены на выделенном Mac mini M4 VPSRox. Узел → SSH → шаги статьи: от нуля до автозагрузки в TestFlight — за один день. Аренда по дням, без контракта.

Стандартная конфигурация
ЧипApple M4 · 38 TOPS
CPU10 ядер (4P + 6E)
Память16 GB unified memory
Сеть1 Gbps выделенный канал
SLA99.9% доступности
ДоставкаАктивация за 1–5 минут