Настройка CI/CD для мобильного приложения через GitLab CI
GitLab CI — оптимальный выбор для команд, которые уже держат репозиторий на GitLab (self-hosted или cloud). Конфигурация в .gitlab-ci.yml, раннеры — GitLab-managed или собственные. Для iOS-сборки нужен зарегистрированный macOS self-hosted runner: GitLab SaaS предоставляет только Linux/Windows-раннеры в стандартных планах.
Self-hosted macOS-раннер для iOS
Регистрация раннера:
# На Mac mini или MacBook, который будет CI-машиной
brew install gitlab-runner
gitlab-runner register \
--url https://gitlab.com \
--registration-token $RUNNER_TOKEN \
--executor shell \
--description "macos-m2-runner"
gitlab-runner start
executor shell — раннер выполняет команды напрямую в shell, без Docker-контейнера. Для iOS это единственный реалистичный вариант, так как Xcode не работает в Docker.
Важно: раннер должен работать как LaunchDaemon, не как пользовательский процесс, иначе при перезагрузке Mac CI останавливается. Настройка через sudo gitlab-runner install --user runner.
Структура .gitlab-ci.yml для iOS + Android
stages:
- test
- build
- distribute
variables:
FASTLANE_SKIP_UPDATE_CHECK: "true"
BUNDLE_PATH: vendor/bundle
.ios_job:
tags:
- macos-m2
before_script:
- bundle install --path $BUNDLE_PATH
.android_job:
image: androidsdk/android-34
tags:
- linux-docker
ios:test:
extends: .ios_job
stage: test
script:
- bundle exec fastlane test
artifacts:
reports:
junit: fastlane/test_output/report.junit
paths:
- fastlane/test_output/
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
ios:beta:
extends: .ios_job
stage: distribute
script:
- bundle exec fastlane beta
environment:
name: beta
rules:
- if: $CI_COMMIT_BRANCH == "main"
needs: [ios:test]
android:build:
extends: .android_job
stage: build
script:
- ./gradlew test assembleRelease
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- .gradle/
- vendor/bundle
artifacts:
paths:
- app/build/outputs/apk/release/
expire_in: 3 days
Code signing через GitLab CI/CD Variables
GitLab хранит секреты в Settings → CI/CD → Variables. Для iOS code signing:
before_script:
- echo "$MATCH_KEYSTORE" | base64 -d > /tmp/match.keystore
- bundle exec fastlane match adhoc --readonly true
MATCH_KEYSTORE и MATCH_PASSWORD — masked variables в GitLab. Masked переменные не выводятся в логах даже при echo.
Для App Store Connect API Key — File-тип переменной с .p8-файлом:
- echo "$ASC_API_KEY" > /tmp/AuthKey.p8
- export APP_STORE_CONNECT_API_KEY_PATH=/tmp/AuthKey.p8
Кэширование
GitLab CI кэш привязан к ключу. Для CocoaPods:
cache:
key:
files:
- Podfile.lock
paths:
- Pods/
- vendor/bundle
key.files — кэш инвалидируется автоматически при изменении Podfile.lock. Для Gradle аналогично через *.gradle*.
Environments и деплой по веткам
ios:staging:
stage: distribute
script:
- bundle exec fastlane beta
environment:
name: staging
rules:
- if: $CI_COMMIT_BRANCH == "develop"
ios:production:
stage: distribute
script:
- bundle exec fastlane release
environment:
name: production
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual # Требует подтверждения в UI
when: manual для production — нажатие кнопки в GitLab UI как gate перед релизом. Полезно, когда QA должен подтвердить перед отправкой в App Store.
Сроки
Базовая настройка (macOS-раннер, test + beta lanes): 3–5 дней. Полная конфигурация с environments, Android-конвейером, кэшированием, review-apps: 1.5–2 недели. Стоимость рассчитывается индивидуально.







