Вывод: Чтобы тестировать Xcode 26 Beta в CI после WWDC 26, не стоит переводить main на macos-latest и надеяться на лучшее — правильный путь: два независимых Runner на удалённом Mac: продакшен остаётся на Xcode 16, beta идёт через отдельный label и изолированный каталог DerivedData.
Три жёстких правила: ① prod job'ы не вызывают sudo xcode-select; ② у beta и prod разные -derivedDataPath; ③ runtime Simulator iOS 27 предустанавливается на beta-Runner один раз, а не скачивается в каждом job.
Границы статьи: почему CI замедляется после WWDC — в WWDC26 iOS CI медленнее; здесь — runbook на 48 часов по изоляции runners.
Через неделю после 10 июня в Slack уже не спрашивают «почему всё тормозит», а конкретно: «на каком Mac ставить beta?», «два Xcode на одной машине?», «кто виноват, если main красный?» Ответ прост и практичен: удалённый Mac mini как self-hosted runner и полное разделение beta-валидации и App Store релизов. Если своих runners ещё нет — начните с руководства по ускорению iOS CI; сравнение узлов и TCO — TCO runners в шести регионах.
WWDC 26 принесла SDK iOS 27, фреймворки Siri AI и более строгие проверки Swift 6 — всё на той же M4, что крутила CI ещё вчера. Команды, смешивающие beta и продакшен, платят дважды: длинные сборки, нестабильные pipeline и main, заблокированный экспериментальными toolchain. Изоляция runners — не enterprise-роскошь, а минимально жизнеспособный ops для тех, кто держит ритм TestFlight и параллельно исследует iOS 27.
Hosted-минуты macos-latest в beta-сезон особенно дороги: каждый job стартует с нуля, заново качает runtime Simulator и обнуляет ключи actions/cache после смены Xcode. Self-hosted на удалённом Mac mini переворачивает логику — диск остаётся тёплым, bootstrap сжимается, вы платите месячную аренду вместо поминутного счёта за каждый неудачный beta-эксперимент. Сравните hosted × ожидаемые beta-прогоны с TCO self-hosted; часто уже от ~15 beta-сборок в неделю отдельный beta-Runner дешевле GitHub Actions. Если команда только начинает iOS CI, параллельно прочитайте почему WWDC замедляет pipeline — там контекст SDK и очередей; здесь — практика разделения машин.
1) Три заблуждения: одна машина «для всего» не экономит
Самое частое решение после WWDC: поставить Xcode 16 и 26 beta на единственный CI-Mac и переключать через xcode-select -s в workflow. На бумаге — минус одна аренда; на практике — дороже второго runner:
- Гонки: два параллельных job — поздний
xcode-selectперезаписывает глобальный путь. Prod Archive может незаметно собраться beta-компилятором. - Смешение кэша: общий
~/Library/Developer/Xcode/DerivedDataсмешивает Swift-модули разных версий. Ошибки-призраки: локально зелёно, CI случайно красно. - Диск и RAM: два Xcode + два runtime Simulator ≈ 80 ГБ+. На 16 ГБ beta full build уходит в swap и тормозит все job'ы, включая те, что должны оставаться на Xcode 16.
Ещё одна иллюзия: «beta только ночью, днём машина свободна для prod». Nightly-cron и push в main в 17:58 всё равно пересекаются. Расписание CI недетерминировано — labels детерминированы. Командам Flutter: flutter build ipa на prod-runner, пробы iOS 27 на beta — см. Flutter iOS CI для раздельных cache-path по lane.
2) Архитектура dual-runner: пулы labels и разделение workflow
Минимально жизнеспособная схема: два удалённых Mac mini M4 — или месячный prod + beta-машина посуточно на время адаптации iOS 27:
| Runner | Label GitHub | Версия Xcode | Job'ы |
|---|---|---|---|
| Prod A | self-hosted, macos, ios-prod | Xcode 16.4 (закреплён) | archive main, TestFlight, release-теги |
| Beta B | self-hosted, macos, xcode26-beta | Xcode 26 beta | ветки ios-27-*, nightly-адаптер, пробы API |
Разделение в workflow через runs-on — beta-ветки никогда не должны триггерить label ios-prod:
name: iOS Production CI
on:
push:
branches: [main, release/*]
jobs:
archive:
runs-on: [self-hosted, macos, ios-prod]
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
DERIVED_DATA: /var/ci/deriveddata/prod
steps:
- uses: actions/checkout@v4
- name: Build & Archive
run: |
xcodebuild -scheme MyApp -configuration Release \
-derivedDataPath "$DERIVED_DATA" \
-archivePath build/MyApp.xcarchive archive
name: iOS 27 Beta Adapter
on:
push:
branches: [ios-27-*, feature/siri-ai-*]
schedule:
- cron: '0 2 * * *'
jobs:
beta-build:
runs-on: [self-hosted, macos, xcode26-beta]
continue-on-error: true
env:
DEVELOPER_DIR: /Applications/Xcode_26_beta.app/Contents/Developer
DERIVED_DATA: /var/ci/deriveddata/beta
steps:
- uses: actions/checkout@v4
- run: xcodebuild -scheme MyApp -sdk iphonesimulator build \
-derivedDataPath "$DERIVED_DATA"
Labels — ops-контракт. Регистрация: документация GitHub self-hosted. Webhook: FAQ OpenClaw CI Runner.
Ограничивайте concurrency на уровне runner: GitHub Actions позволяет несколько параллельных job на один label — на 16 ГБ это ведёт к OOM при смешении beta и prod. Задайте concurrency: group: ios-prod и ios-beta в workflow, чтобы на lane выполнялся максимум один тяжёлый archive. Это важнее очередного «оптимизационного» флага в YAML.
3) Установка Xcode 26 beta на удалённом Mac и сосуществование версий
На bare-metal удалённом Mac два Xcode рядом в /Applications — явные имена папок, чтобы beta не перезаписала stable:
- Скачать Xcode 26 beta (.xip) с Apple Developer Downloads, распаковать по SSH в
/Applications/Xcode_26_beta.app. - Prod-runner: только абсолютный
DEVELOPER_DIRв workflow — безsudo xcode-select -switchв job'ах. xcodebuild -runFirstLaunchи принятие лицензии один раз вручную — не в каждый CI job.- Runtime Simulator iOS 27 предустановить на beta-машине; убрать
-downloadPlatformиз workflow.
Локальным разработчикам с двумя версиями — медленный Xcode? Cloud Mac mini: писать локально, тяжёлые сборки на M4 в облаке. Почему Simulator/Instruments только на macOS: toolchain Xcode только macOS. После первой установки beta проверьте, что prod-runner не видит /Applications/Xcode_26_beta.app в PATH job'ов — только явный DEVELOPER_DIR в prod workflow.
4) DerivedData / Pods / SPM — тройная изоляция кэша
Инвалидация после WWDC — от смены формата компилятора/модулей, не от «сломанного кэша». Стратегия:
| Тип кэша | Путь prod | Путь beta | Примечание |
|---|---|---|---|
| DerivedData | /var/ci/deriveddata/prod | /var/ci/deriveddata/beta | фиксированный -derivedDataPath |
| CocoaPods | /var/ci/cocoapods/prod | /var/ci/cocoapods/beta | CP_HOME_DIR |
| SPM | /var/ci/spm/prod | /var/ci/spm/beta | clonedSourcePackagesDirPath |
| ModuleCache | под деревом prod | отдельное дерево beta | не делить ~/Library |
actions/cache в WWDC-сезон редко хватает: ключи обнуляются после major Xcode; гигабайты DerivedData по сети часто медленнее локального диска. Ценность self-hosted — персистентный диск: вторая beta-сборка падает с 20+ до ~10 мин. Тройной кэш Flutter: Flutter iOS CI; подпись TestFlight и Match: руководство self-hosted (Fastlane описан там же — отдельной статьи Fastlane в русской версии блога пока нет, но workflow подписи полностью покрыт).
sudo mkdir -p /var/ci/{deriveddata,cocoapods,spm}/{prod,beta}
sudo chown -R $(whoami) /var/ci
du -sh /var/ci/deriveddata/*
5) Типичные ловушки: падения beta, раздувание SDK, путаница keychain
- Beta красная блокирует merge:
continue-on-error: true; beta никогда не required check. - Prod использует beta SDK: проверять
DTXcodeиDEVELOPER_DIRв логах archive. - Keychain / Match: отдельный login keychain на runner;
git_urlMatch можно общий, пароль keychain — per machine. Матрица поддержки Xcode Apple. - Диск полон: SSD 512 ГБ на beta; weekly cron удаляет beta-подпапки старше 14 дней.
- Параллельные archive OOM: M4 16 ГБ — concurrency prod = 1.
- Слишком строгая branch protection: required checks без разделения beta/prod — отдельные статусы по label.
- Flutter + native:
flutter build ipaиxcodebuildне делят DerivedData вслепую — см. Flutter iOS CI.
Держите одностраничный runbook: какая машина несёт какой label, кто обновляет beta-Xcode, rollback если beta-сборка задела prod-keychain. Дешевле, чем weekend «CI зелёный локально, красный удалённо». Раз в неделю сверяйте версию Xcode на prod-runner с тем, что указано в App Store Connect для текущих submission — расхождение между «мы думаем, что на 16.4» и «на диске случайно beta» — частая причина отклонённых билдов, которую изоляция runners снимает сразу после внедрения.
6) Пример: P50 до и после split runner (не SLA)
| Метрика | Без split (main на beta) | Изоляция dual-runner |
|---|---|---|
| P50 archive main | ~42 мин | ~11 мин |
| P50 beta-адаптер | (конкуренция с prod) | ~22 мин нед.1, ~12 мин нед.2 |
| Сбои main из-за beta | высокие | ~0 |
| Число Mac | 1 | 2× M4 или prod + посуточная beta |
48-часовой A/B на своём репозитории. Ключевая метрика: main больше не платит за beta. Смотрите и дисперсию P50–P95 на main — меньше алертов «вчера 11, сегодня 38». Если после split prod всё ещё медленный, проблема не в beta — вернитесь к WWDC-статье и проверьте кэш CocoaPods/SPM на prod-lane отдельно от beta.
7) Чеклист внедрения за 48 часов
- День 0 утро: зафиксировать Xcode prod,
DEVELOPER_DIRв prod workflow. - День 0 день: удалённый Mac mini (аренда 48 ч) зарегистрировать как
xcode26-beta. - День 1: создать
/var/ci/...; первый full build beta; prod без изменений зелёный. - День 2: beta на nightly + ветки
ios-27-*; убрать beta check из branch protection. - Приёмка: три archive main < 15 мин; beta красная без Slack @channel.
Вторая машина на месяц? Сначала beta-spike посуточно — TCO: MacBook Pro vs облачный Mac и TCO шесть регионов. Зафиксируйте шаги в team wiki: кто трогает prod-workflow, кто обновляет beta-Xcode, какие Slack-алерты при красной beta должны молчать — иначе схема развалится, когда инженер, всё настроивший, уйдёт в отпуск.
8) Частые вопросы (15 пунктов)
1. Два Xcode на удалённом Mac? Да — разные папки .app; CI через DEVELOPER_DIR.
2. Beta-runner нужно 24 ГБ RAM? 16 ГБ M4 часто хватает; параллельные multi-target archive → 24 ГБ.
3. Prod иногда гоняет beta job'ы? Не рекомендуется — минимум разный DerivedData; лучше разные машины.
4. macos-latest заменит beta-runner? Нет — эфемерный диск, bootstrap 15+ мин в WWDC-сезон.
5. actions/cache хватит? Ключи обнуляются после major Xcode; сеть медленнее локального диска.
6. Beta нестабильна, CI весь красный? Ожидаемо — beta lane опциональна.
7. Как проверить, что prod без beta-компилятора? DTXcode или assertion DEVELOPER_DIR.
8. Runtime Simulator каждый job? Нет — один раз на beta-машине.
9. Runners в разных регионах? Да — prod ближе к команде.
10. OpenClaw для beta? FAQ OpenClaw CI.
11. Посуточной аренды хватит? Для spike адаптации да; долгий nightly → месяц.
12. Когда чистить DerivedData? Lane > 40 ГБ — сначала beta.
13. Swift 6 strict? Только beta lane; prod — постепенная миграция.
14. Связь со статьёй WWDC? Та — «почему медленно»; эта — «как разделить runners».
15. Две машины за 48 ч обязательны? Нет — без beta на main хватит prod; beta lane когда готова посуточная машина. Отдельной статьи Fastlane в ru-блоге нет — подпись, Match и загрузка TestFlight описаны в self-hosted-гайде; на beta-машину prod-сертификаты не импортируйте без нужды.
Beta может экспериментировать — prod обязан быть стабильным
После WWDC 26 часто выгоднее всего месячный prod-runner + beta-машина по запросу: Mac mini M4 7×24 для TestFlight, beta посуточно для адаптации iOS 27. Nuvcloud bare metal — DerivedData переживает job'ы; M4 16 ГБ хватает большинству iOS archive, 24 ГБ для параллельных schemes. Тихий, экономичный, CI без присмотра.
Планируете Xcode 26 Beta CI без превращения main в полигон? Nuvcloud Mac mini M4 в облаке — самая дешёвая точка старта для split — смотреть цены , разделить beta и prod за 48 часов.