Настройка CI/CD для iOS-приложения через Fastlane
Fastlane — де-факто стандарт автоматизации iOS-сборок за пределами Apple-экосистемы. Он работает на любом CI: GitHub Actions, GitLab CI, Jenkins, Bitrise. Основное преимущество перед Xcode Cloud — полный контроль над каждым шагом и возможность запустить ту же команду локально, что получит CI.
Почему code signing — главная проблема
iOS требует валидный provisioning profile и сертификат для любой сборки не в симуляторе. На локальной машине они есть в Keychain. На CI-сервере — нет. Без правильной настройки fastlane каждый раз завершается с Code Signing Error: No profiles for bundle ID found.
fastlane match решает это: все сертификаты и профили хранятся зашифрованными в отдельном Git-репозитории (или S3/Google Cloud). На CI — одна команда bundle exec fastlane match adhoc скачивает и устанавливает нужный профиль. Passphrase к match — секретная переменная CI.
Схема работы match:
Git-репозиторий (зашифрованный) ←→ fastlane match ←→ Apple Developer Portal
↓
CI Keychain (временный)
Для команды из 5+ разработчиков match в readonly-режиме на CI и в обычном на машинах разработчиков — стандартная конфигурация.
Структура Fastfile
default_platform(:ios)
platform :ios do
before_all do
setup_ci if ENV['CI'] # Создаёт временный Keychain на CI
end
lane :test do
run_tests(
scheme: "MyApp",
devices: ["iPhone 15", "iPhone SE (3rd generation)"],
code_coverage: true
)
end
lane :beta do
match(type: "adhoc", readonly: true)
increment_build_number(
build_number: ENV["CI_PIPELINE_ID"] || Time.now.to_i.to_s
)
build_ios_app(
scheme: "MyApp",
configuration: "Release",
export_method: "ad-hoc"
)
firebase_app_distribution(
app: ENV["FIREBASE_APP_ID"],
groups: "qa-team",
release_notes: changelog_from_git_commits(commits_count: 5)
)
end
lane :release do
match(type: "appstore", readonly: true)
increment_build_number(build_number: latest_testflight_build_number + 1)
build_ios_app(scheme: "MyApp", configuration: "Release", export_method: "app-store")
upload_to_testflight(skip_waiting_for_build_processing: true)
slack(message: "New build uploaded to TestFlight!", channel: "#releases")
end
end
setup_ci создаёт временный Keychain в рамках CI-джоба. Без этого fastlane попытается открыть пользовательский Keychain, который на headless CI недоступен.
Номер сборки (build number)
increment_build_number без аргумента читает текущий номер из Info.plist и увеличивает на 1. Но при параллельных CI-джобах возможны коллизии — два PR собирают одновременно и оба получают одинаковый номер. Решение: использовать CI_PIPELINE_ID (GitLab) или github.run_number (GitHub Actions) как build number. Это гарантирует уникальность.
Интеграция с GitHub Actions
- name: Run Fastlane Beta
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_AUTH }}
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
FIREBASE_TOKEN: ${{ secrets.FIREBASE_CLI_TOKEN }}
run: bundle exec fastlane beta
Раннер должен быть macOS (runs-on: macos-14). Linux-раннеры для iOS-сборки не подходят — Xcode работает только на macOS.
Кэширование зависимостей
Самый долгий шаг — pod install или swift package resolve. На GitHub Actions кэшируем:
- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('Podfile.lock') }}
Экономит 3–8 минут на каждом прогоне при неизменном Podfile.lock.
Сроки
Базовая настройка Fastlane с match, test и beta лейнами на GitHub Actions: 3–5 дней. Полная конфигурация с release-лейном, changelog, Slack-уведомлениями, кэшированием, multi-scheme поддержкой: 1–2 недели. Стоимость рассчитывается индивидуально.







