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

Self-hosted runner
для GitHub Actions

$19.8 / день · выделенная физическая машина
Заказать Mac для CI/CD
Полный Xcode Доступ по SSH

Mac в облаке для Xcode CI/CD: GitHub Actions, Jenkins и сборка iOS

Каждый push ждёт свободный macOS Runner? Ночная сборка забивает память MacBook? В этой заметке — на выделенном Mac mini M4 ZovCloud: от базовой среды Xcode и импорта сертификатов до подключения self-hosted runner GitHub Actions и Jenkins Agent, подписи Archive и загрузки в TestFlight — с данными о времени на реальном SwiftUI-проекте и заметками о проблемах с Keychain.

Почему цепочка Apple привязана к железу macOS

В отличие от Android, где Gradle работает в Linux-контейнере, цепочка доставки iOS и macOS — xcodebuild, codesign, notarytool, App Store Connect API — требует ОС и чипа с подписью Apple. Это не вопрос «поставить эмулятор и обойти»: цепочка сертификатов Archive и App Store, оптимизации компилятора Swift под Apple Silicon — всё предполагает, что под вами настоящий Mac.

Поэтому каждая команда с iOS-продуктом рано или поздно отвечает на один вопрос: Кто будет долгосрочно предоставлять этот Mac? Три типичных пути — macOS Runner GitHub, свой Mac mini в офисе, выделенная физическая машина в облаке — нет абсолютного лучшего; разница в терпимости к очередям, частоте сборок и наличии человека для обслуживания системы и сертификатов. Статья о технической реализации третьего пути, но сначала — границы двух других.

Среда нашего теста

Железо: Mac mini M4 · 10-ядерный CPU · 16 GB унифицированной памяти · 256 GB NVMe · 1 Gbps выделенный канал (узел ZovCloud в Японии).
Система: macOS 15 Sequoia, Xcode 16.4. Пример: среднее SwiftUI-приложение (~118 тыс. строк, 3 Extension target).
CI: GitHub Actions self-hosted runner 2.323.0; Jenkins 2.479 LTS + macOS agent.
Подпись: сертификат Apple Distribution + App Store Connect API Key (Issuer ID + Key ID + .p8).

Разбор времени CI: ожидание, сборка, подпись

Многие жалуются, что «CI медленный», не разделяя общее время на три части: ожидание машины, локальная сборка и линковка, подпись и загрузка. На одном commit сравнили Runner macos-14 GitHub с выделенным узлом M4 ZovCloud — получили показательные цифры.

Управляемый Runner в пик (UTC 13:00–17:00) в среднем ждал 22 минуты до старта job; сам xcodebuild archive занял 5 мин 38 с. На выделенном M4 без очереди полная сборка Archive — 3 мин 58 с; параллельная компиляция Swift на 10-ядерном M4 быстрее старых Intel CI, 16 GB унифицированной памяти при clean build не вызвали swap.

3m 58s Время Archive на M4
22m Очередь Runner в пик
1–5 min Активация узла ZovCloud
16 GB Унифицированная память (без swap)
Путь сборки Типичная месячная стоимость Очередь / параллелизм Более подходящий сценарий
Управляемый macOS Runner GitHub Поминутная оплата (от ~$0.08/мин) Общий пул, заметные очереди в пик < 500 минут сборок в месяц, приемлемое ожидание
Свой Mac mini в серверной Разовая покупка железа + электричество и обслуживание Эксклюзивно, обновления системы — на вас Постоянный офис, частые сборки круглый год
Выделенный Mac в облаке (по дням) ZovCloud от $19.8/день, без контракта Выделенная физическая машина, доступ сразу после оплаты Малые команды, сборки в неделю релиза, удалённая работа

Если боль — «полчаса после push, чтобы узнать, прошла ли сборка», узкое место обычно в очереди, а не в Xcode. Закрепить Runner на всегда онлайн выделенном Mac — самый прямой способ сократить цикл обратной связи — с раздела 3 — как подготовить эту машину как воспроизводимую базу сборки.

Воспроизводимая база сборки Xcode на Mac в облаке

Mac mini ZovCloud поставляется с полным macOS и правами администратора. После активации войдите по SSH или браузерному VNC; рекомендуем фиксированную структуру каталогов для сертификатов и профилей, чтобы скрипты Runner не искали пути по-разному. Четыре шага — наш стандартный чеклист инициализации каждого нового узла.

  1. 01
    Установить Xcode и принять лицензию

    Установите Xcode 16.x из App Store, выполните sudo xcodebuild -license accept и xcodebuild -runFirstLaunch. Проверка: xcodebuild -version — ожидаемая версия; xcode-select -p указывает на /Applications/Xcode.app/Contents/Developer.

  2. 02
    Импорт сертификата Distribution и профилей

    Передайте .p12 по защищённому каналу в ~/certs/, импортируйте через security import в отдельную связку ~/Library/Keychains/ci.keychain-db; .mobileprovision — в ~/Library/MobileDevice/Provisioning Profiles/. Настройте доверие partition list для codesign, чтобы не было диалогов при unattended-сборке.

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

    Создайте API Key в Apple Developer, сохраните AuthKey_XXXXXX.p8 в ~/private_keys/. Для TestFlight — xcrun altool или Fastlane pilot upload, без интерактивной 2FA Apple ID.

  4. 04
    Первый полный Archive и сохранение DerivedData

    Клонируйте репозиторий и локально пройдите Release Archive, убедитесь в цепочке подписи. 256 GB системного диска достаточно для среднего проекта; DerivedData ускоряет инкрементальные сборки на ~30–45%. При многих Swift Package укажите -clonedSourcePackagesDirPath для кэша.

Рекомендуемые переменные окружения

В ~/.zprofile или скрипте запуска Runner экспортируйте единообразно KEYCHAIN_PATH, P8_KEY_PATH, DEVELOPER_DIR, чтобы GitHub Actions и Jenkins использовали одни пути и не было «локально собирается, в CI нет сертификата».

Регистрация self-hosted Runner GitHub Actions и маршрутизация workflow

После регистрации self-hosted Runner workflow по label направляет job на этот Mac в облаке, минуя общий пул. Путь: Settings репозитория → Actions → Runners → New self-hosted runner, выберите macOS ARM64, скачайте пакет actions-runner и выполните:

./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token RUNNER_TOKEN --labels macos-m4,zovcloud,ios-build --unattended

После регистрации установите как системный сервис, чтобы Runner поднимался после перезагрузки: sudo ./svc.sh installsudo ./svc.sh start. В YAML workflow укажите label:

runs-on: [self-hosted, macos-m4]

Типичная цепочка iOS job: checkout → разблокировка CI keychain → xcodebuild archivexcodebuild -exportArchive → Fastlane upload_to_testflight или altool --upload-app. На узле M4 от push до обработки TestFlight (с очередью Apple) в среднем ~10 минут, из них локальная сборка и загрузка — 5–6 минут.

Границы безопасности self-hosted Runner

Runner видит исходники и ключи подписи — ограничьте права collaborators, регулярно меняйте registration token, не запускайте workflow с секретами автоматически на PR из fork. При нескольких проектах на одном узле — отдельные Runner на репозиторий или песочница OpenClaw для ограничения доступа к файловой системе.

Подключение Jenkins Agent и ключевые моменты pipeline

Если у команды уже есть контроллер Jenkins (может на Linux), сборки macOS идут через Agent. На Mac в облаке установите JDK 17, скачайте agent.jar, запустите как LaunchDaemon, контроллер отдаёт задачи по SSH или JNLP.

Сила Jenkins — визуальные pipeline и плагины: Credentials Binding для пароля keychain, AnsiColor для логов, архив артефактов в Artifactory и т.д. Типичный Pipeline вызывает sh 'xcodebuild ...' в stage('Archive'), Fastlane в stage('Upload'). По сравнению с GitHub Actions Jenkins лучше для многоветочных, многосредовых процессов с ручным approval внутри компании.

Mac в облаке по дням — «эластичный Agent»: на неделю релиза подключите Jenkins, в спокойный период освободите — не нужно круглый год обслуживать Mac в серверной. Фиксированный DEVELOPER_DIR против путаницы версий Xcode — часто упускаемая деталь стабильности Jenkins.

Замкнутый цикл Archive → Export → TestFlight из командной строки

И в GitHub Actions, и в Jenkins цепочка одна: Archive → .xcarchive → Export → .ipa → загрузка в App Store Connect. Командная строка — стандарт CI, без GUI Xcode.

Пример Archive (Release, указанный scheme):

xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -archivePath build/MyApp.xcarchive CODE_SIGN_STYLE=Manual PROVISIONING_PROFILE_SPECIFIER="MyApp AppStore"

Export с ExportOptions.plist (method app-store):

xcodebuild -exportArchive -archivePath build/MyApp.xcarchive -exportPath build/export -exportOptionsPlist ExportOptions.plist

Загрузка в TestFlight (API Key, для unattended):

xcrun altool --upload-app -f build/export/MyApp.ipa -t ios --apiKey KEY_ID --apiIssuer ISSUER_ID

10-ядерный CPU M4 заметно ускоряет параллельную компиляцию Swift против старых Intel CI; при многих Swift Package кэшируйте в workflow ~/Library/Developer/Xcode/DerivedData и каталог SourcePackages, со второй сборки время сокращается примерно на треть.

Keychain и подпись в unattended-среде

Сбой codesign по SSH или на headless Runner почти всегда из-за keychain, а не просроченного сертификата. В начале скрипта сборки разблокируйте и авторизуйте:

security unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/ci.keychain-db

security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" ~/Library/Keychains/ci.keychain-db

Пароль через GitHub Secrets или Jenkins Credentials — никогда в открытом виде в репозитории. При errSecInternalComponent проверьте default keychain и доверие codesign в partition list. Если altool зависает на аутентификации — сверьте Issuer ID API Key с именем .p8 и права «Developer» у ключа.

Частая ловушка — несовпадение Provisioning Profile и Bundle ID: на Archive не падает, на Export — да. Добавьте в CI security cms -D -i profile.mobileprovision для UUID и сверьте с PROVISIONING_PROFILE_SPECIFIER в проекте Xcode.

Эластичная мощность macOS: когда вынести Runner на выделенный узел в облаке

Инди и малые команды в дилемме: нужны сборки macOS, но не хочется арендовать офис, тянуть линию и решать отключения и обновления ради одной CI-машины. Linux VM в публичном облаке не подходит (нет полного macOS и цепочки Apple); Mac mini дома — нестабильный uplink, смена IP, случайное выключение.

ZovCloud даёт выделенный физический Mac mini M4: без виртуализации и перепродажи, 16 GB памяти и 1 Gbps выделенный канал на машину, активация за 1–5 минут после оплаты. Пять узлов — Сингапур, Япония, Корея, Гонконг, восток США — по географии пользователей; для App на японский рынок — Токио, меньше задержка при загрузке в TestFlight.

От $19.8/день, $53.5/неделю, $99.1/месяц, без долгого контракта. Неделя плотных релизов — узел и Runner; в спокойный период — освободить, часто выгоднее года своего железа и электричества. Для параллельного Archive на нескольких машинах — кластер Thunderbolt 5 80 Gbps, для крупного monorepo или матрицы App.

  1. 01
    Выбрать узел и активировать в ZovCloud

    В консоли выберите регион и срок — после оплаты SSH и VNC выдаются автоматически. Подключение — в центре помощи.

  2. 02
    По разделу 3 — база Xcode и подписи

    Пути keychain и API Key — в переменные окружения для GitHub Actions / Jenkins.

  3. 03
    Зарегистрировать Runner и прогнать первый pipeline

    Сначала Debug для проверки сборки, затем Release Archive + TestFlight, закрепите Fastlane lane в репозитории.

Тип команды Рекомендация Роль Mac в облаке
Инди, 1–2 релиза в месяц Аренда по дням в день релиза + ручной Archive Временная сборочная машина
5–15 человек, много push в день Постоянный self-hosted Runner Помесячная аренда, без очереди
Есть Jenkins, нет macOS Agent Mac в облаке как эластичный Agent Пиковая мощность без нового железа
Матрица приложений + ночные пакетные сборки Кластер TB5 Параллельная Archive на нескольких M4

Планка iOS CI/CD — не меню Xcode, а стабильная предсказуемая мощность macOS плюс одна доверенная среда подписи. Публичные Runner подходят для редких сборок; своя стойка — для команд с ops-ресурсами; выделенный Mac в облаке закрывает разрыв между «не хочу очередей» и «не хочу покупать железо». Перенесите компиляцию в облако — MacBook сосредоточится на коде, а не на вентиляторах и памяти, украденной ночным CI.

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

Дайте iOS pipeline macOS-сборщик без очередей

Выделенные узлы ZovCloud Mac mini M4: полный macOS и Xcode, 16 GB унифицированной памяти, доступ SSH / VNC, готовность к Runner GitHub Actions и Jenkins, от $19.8/день.

$19.8 / день от
ЧипApple M4 · 38 TOPS
CPU10 ядер выделено
Память16 GB унифицированной
Пропускная способность1 Гбит/с выделенный
SLA99.9%
Доставка1–5 минут