В коротком цикле релиза React Native / Expo команда платит не только минутами компиляции, но и вариацией задержки в очереди облачных билдов EAS: когда несколько веток одновременно гонят iOS-артефакты, хвост очереди превращается в скрытый налог на ревью и хотфиксы. Продакт и QA воспринимают задержку как «ещё пятнадцать минут», инженеры видят рост p95 и размытие причинно-следственной связи между коммитом и IPA.
Облачная сборка удобна для предсказуемого билд-агента, но в пик она конкурирует за слоты с соседними проектами и пайплайнами. Перенос тяжёлого этапа на eas build --local на выделенном macOS-узле — практичный гибрид: вы сохраняете тот же CLI и профили eas.json, но контролируете CPU, диск и время старта job. Такой подход особенно полезен, когда одновременно нужны предпросмотры для дизайна, ночные регрессионные сборки и ручные релизные теги — ровно там очередь облака начинает стоить дороже минут в биллинговой строке.
Очередь EAS и зачем вообще «локально» на облачном Mac
Для iOS путь EAS часто включает prebuild, CocoaPods и Xcode — это не «чистый JS». Если параллельно идут предпросмотры, авто-сборки из GitHub Actions и ручные билды, очередь становится узким местом быстрее, чем упираетесь в тарифные минуты. eas build --local исполняет ту же логику на вашей машине; в паре с посуточным облачным Mac Runner вы получаете аренду железа без покупки станции и без долгой очереди чужого пула. Цена вопроса — поддержка образа macOS, версии Xcode и секретов так, как это делает зрелый self-hosted CI. Для сравнения паттернов shell-runner и тегов см. материал про GitLab self-hosted macOS Runner на облачном Mac.
Инъекция секретов: Expo, Apple и CI без смешивания ролей
Минимальный набор для автоматической подписи через EAS: токен CLI (EXPO_TOKEN или интерактивная сессия), доступ к учётке Expo и секреты Apple (ключ API App Store Connect либо управляемые сертификаты через EAS credentials). На runner храните короткоживущие токены в секретах оркестратора (GitHub Actions / GitLab CI / собственный launcher), а не в dotfiles пользователя. Разведите роли: отдельный «build-bot» Apple ID только под CI, отдельный PAT к Git без прав администрирования репозитория.
После старта джобы экспортируйте переменные в изолированную shell-сессию и запускайте eas build --local --platform ios --profile production; по завершении очищайте временные ключи и артефакты из рабочего каталога. Если управляете кэшем удалённо, не кладите в архив каталоги с PEM или .p12 — только производные кэши зависимостей. Для двухфакторных сценариев предпочитайте ключи App Store Connect и ограничение по IP или короткоживущие OIDC-токены там, где поддерживает ваш оркестратор: это проще отозвать, чем пароли сервисных учёток.
При командной работе фиксируйте политику внутри репозитория: кто может выполнять eas credentials, где хранится резервная копия сертификатов и как часто ротируются секреты. Документ в двух страницах снимает половину инцидентов «почему билд вчера был зелёным». Здесь же уместно держать ссылку на регламент доступа к продакшен-профилям и матрицу ответственности — без этого даже идеальный кэш не спасёт календарь релиза.
Ключи кэша: что должно попасть в хэш, чтобы не ломать воспроизводимость
Кэш для Expo-iOS многослойный: lockfile JavaScript (package-lock.json / yarn.lock / pnpm-lock.yaml), версия expo и SDK, содержимое eas.json для выбранного профиля, образ native после prebuild (если он закоммичен или генерируется на лету), а также мажорная версия Xcode и инструментов командной строки. Практическая формула ключа: hash(lockfiles) + expo.sdkVersion + eas.profile + xcode.select + runner.imageTag. Для ускорения безопасно переиспользовать кэши CocoaPods и DerivedData только при совпадении этих компонентов; иначе вы получите «зелёный» билд с неявным дрейфом бинарника.
Отдельно учитывайте переменные окружения, влияющие на нативную сборку: например, включённые экспериментальные флаги новой архитектуры или кастомный FASTLANE_SKIP_UPDATE_CHECK. Любое изменение, которое меняет входные данные компилятора, должно попадать в ключ или принудительно сбрасывать слой кэша. Храните журнал соответствия «ключ → успешный билд» хотя бы неделю — это ускорит разбор странных регрессий после обновления Expo SDK. Общие принципы выравнивания локального и удалённого кэша на macOS-runner описаны в заметке о коротком цикле мобильной сборки и недельной аренде узла — матрица там другая, но логика ключей и аренды совпадает.
# Пин версий до установки зависимостей node -v && npm ci # Локальный iOS-билд через EAS eas build --local --platform ios --profile production --non-interactive # Кэши: версионируйте tarball по ключу из lockfile + Xcode
Матрица решений: минутный пакет EAS vs недельная аренда облачного Mac
Выбирайте модель по форме нагрузки, а не по «ощущению дешевизны» строки в счёте. Минутные пакеты хорошо масштабируются вниз: оплатили — построили — забыли. Недельная аренда выигрывает, когда суммарное время ожидания и повторных установок зависимостей превышает административную стоимость поддержки образа.
Добавьте к сравнению исходящий канал до CDN Expo и App Store Connect: локальный билд всё равно загрузит артефакт, и узкий аплинк на арендованном Mac способен съесть выигрыш от отсутствия очереди. Замерьте скорость загрузки `.ipa` так же придирчиво, как компиляцию. Если команда распределена географически, выберите регион runner ближе к разработчикам, которые чаще всего триггерят сборку — это уменьшает время интерактивной отладки на стейджинге.
| Сигнал | Минуты / pay-as-you-go EAS | Недельный облачный Mac + local build |
|---|---|---|
| Всплески перед релизом, редкие билды | Обычно проще — не держите простой образ | Избыточно, если узел простаивает |
| Ежедневные билды из нескольких веток | Очередь и конкуренция слотов бьют по p95 | Стабильный wall time, фиксированный SLA |
| Строгая изоляция секретов | Меньше поверхности на вашей стороне | Нужна дисциплина runner и ротация ключей |
| Большие кэши Pods / DerivedData | Каждый чистый слот тянет зависимости заново | NVMe + сохранённый слой кэша между job |
FAQ: короткие ответы без воды
Обязательен ли доступ в интернет для eas build --local? Да, CLI синхронизирует проект с сервисами Expo и загружает артефакты; закройте исходящий трафик только если осознанно поднимаете air-gapped зеркало.
Совместимо ли с монорепозиторием? Да, но ключ кэша должен включать путь к приложению и workspace.lock на границе пакета — иначе кэш «протечёт» между приложениями.
Что делать при смене минорной версии Xcode на runner? Инвалидируйте DerivedData и Pods-кэш или используйте новый тег образа; частичный reuse здесь дороже полного rebuild.
Можно ли чередовать облачный и локальный режим в одном репозитории? Да: держите два профиля в eas.json и разные workflow в CI — главное, чтобы версии зависимостей и ключи кэша совпадали, иначе получите расхождение бинарников между каналами.
Нужен ли отдельный диск под артефакты? Желательно: выносите ~/Library/Developer/Xcode/DerivedData и каталоги промежуточных билдов на быстрый том; это снижает фрагментацию и упрощает очистку между job без затрагивания системного раздела.
На облачном Mac mini M4 пайплайн Expo держит темп
Для eas build --local критичны стабильная связка Xcode / CLI и предсказуемый NVMe-под том для DerivedData и Pods. Apple Silicon M4 на macOS даёт нативную среду без эмуляторов и «костылей» вокруг инструментов Apple; Homebrew, Node и Watchman работают так же, как на офисной машине, что сокращает время наведения порядка на runner.
Низкое энергопотребление в простое (порядка 4 Вт) и бесшумный корпус делают Mac mini удобным узлом для длительных ночных билдов, а Gatekeeper вместе с SIP снижают класс рисков по сравнению с типичным самособранным ПК. Для команд с коротким циклом это означает меньше незапланированных простоев и более ровный график релизов.
Если вы переводите Expo iOS с общей очереди на управляемый Mac-runner, VPSSpark — практичная точка входа в выделенный облачный Mac mini M4 — узнайте тарифы и конфигурации и зафиксируйте свой SLO по сборке на железе, которое не нужно покупать и охлаждать в офисе.