なぜ専用 Mac で Fastlane なのか?
Fastlane は、iOS と Android の開発における標準的な自動化ツールです。コード署名から TestFlight へのデプロイまで、あらゆる処理を扱います。専用 Mac Mini M4 で Fastlane を実行すると、次のメリットが得られます。
コード署名証明書が実行間で保持されます。ビルドのたびにインポート/エクスポートする必要がありません。
DerivedData が保持されるため、fastlane build が 15 分以上ではなく 2〜4 分で完了します。
fastlane snapshot を実際のシミュレーターで実行し、すべてのデバイスサイズに対応します。
安定した専用Macサーバーなら、不安定なデプロイやコード署名の問題が減ります。
Fastlane をインストールする
専用 Mac mini M4 ビルドサーバーに SSH で接続し、Fastlane をインストールします。CI には Bundler をおすすめします。Gemfile で Fastlane のバージョンを固定すれば、毎回同じバージョンで実行できます。Homebrew はローカルでの利用に向いています。macOS のシステム Ruby は使わないでください。現在の Fastlane には Ruby 3.1 以降が必要です。
選択肢 A: Bundler と Gemfile(CI に推奨)
# Ruby 3.1 or later (current Fastlane releases need it; the macOS system Ruby is older)
# Homebrew not installed yet? Install it first, see Option B.
brew install ruby
echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zprofile
export PATH="/opt/homebrew/opt/ruby/bin:$PATH"
# In your project, a Gemfile that pins Fastlane
cd /path/to/your/ios-project
cat > Gemfile <<'EOF'
source "https://rubygems.org"
gem "fastlane", "~> 2.240"
gem "cocoapods" # if using CocoaPods
EOF
bundle install
# Commit Gemfile and Gemfile.lock, then always run Fastlane through Bundler
bundle exec fastlane --version
選択肢 B: Homebrew でインストールする(ローカル用)
# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# Install Fastlane
brew install fastlane
# Verify installation
fastlane --version
プロジェクトで Fastlane を初期化する
# Navigate to your project directory
cd /path/to/your/ios-project
# Initialize Fastlane (with the Homebrew install, run: fastlane init)
bundle exec fastlane init
# Choose option 4: "Manual setup"
# This creates the fastlane/ directory with Appfile and Fastfile
コード署名のために Match を設定する
Fastlane Match は、コード署名証明書とプロビジョニングプロファイルを、プライベートな Git リポジトリまたはクラウドストレージに保存します。これにより、すべてのマシン(およびチームメンバー)が同じ署名 ID を使えるようになります。
Match を初期化する
# Initialize match (choose "git" for storage)
fastlane match init
# This creates fastlane/Matchfile
Matchfile を設定する
# fastlane/Matchfile
git_url("https://github.com/your-org/ios-certificates.git")
storage_mode("git")
type("appstore") # default type, can be overridden per lane
app_identifier(["com.yourcompany.myapp"])
username("your-apple-id@example.com")
# For CI environments, use App Store Connect API key instead of username/password
# api_key_path("fastlane/AuthKey.json")
証明書を生成する
# Generate development certificates and profiles
fastlane match development
# Generate App Store distribution certificates and profiles
fastlane match appstore
# For ad-hoc distribution
fastlane match adhoc
# On CI, use readonly mode to avoid accidentally creating new certs
fastlane match appstore --readonly
Fastfile を作成する(ビルド、テスト、デプロイのレーン)
iOS アプリのビルド、テスト、デプロイのためのレーンを含む、完全な Fastfile を示します。
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
# ---- SHARED ----
before_all do
setup_ci if ENV['CI'] # Configures keychain for CI environments
end
# ---- BUILD ----
desc "Build the app for testing"
lane :build do
match(type: "development", readonly: true)
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
configuration: "Debug",
destination: "generic/platform=iOS Simulator",
# Outside the checkout, so it survives the next git clean
derived_data_path: File.expand_path("~/DerivedData/MyApp"),
skip_archive: true,
skip_codesigning: true
)
end
# ---- TEST ----
desc "Run all unit and UI tests"
lane :test do
run_tests(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
devices: ["iPhone 17"], # any device from: xcrun simctl list devices available
derived_data_path: File.expand_path("~/DerivedData/MyApp"),
result_bundle: true,
output_directory: "fastlane/test_results",
parallel_testing: true,
concurrent_workers: 4
)
end
# ---- BETA ----
desc "Build and push a new beta to TestFlight"
lane :beta do
# Ensure we are on a clean git state
ensure_git_status_clean
# Fetch App Store certificates
match(type: "appstore", readonly: true)
# Increment build number (the API key avoids an Apple ID login and 2FA)
increment_build_number(
build_number: latest_testflight_build_number(
api_key_path: "fastlane/AuthKey.json"
) + 1
)
# Build the app
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
derived_data_path: File.expand_path("~/DerivedData/MyApp"),
output_directory: "fastlane/builds"
)
# Upload to TestFlight
upload_to_testflight(
skip_waiting_for_build_processing: true,
api_key_path: "fastlane/AuthKey.json"
)
# Commit the version bump
commit_version_bump(
message: "chore: bump build number [skip ci]",
force: true
)
# Tag the release
add_git_tag(
tag: "beta/#{lane_context[SharedValues::BUILD_NUMBER]}"
)
push_to_git_remote
end
# ---- RELEASE ----
desc "Build and submit to App Store Review"
lane :release do
match(type: "appstore", readonly: true)
# Increment version number (patch)
increment_version_number(bump_type: "patch")
increment_build_number(
build_number: latest_testflight_build_number(
api_key_path: "fastlane/AuthKey.json"
) + 1
)
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
derived_data_path: File.expand_path("~/DerivedData/MyApp")
)
upload_to_app_store(
submit_for_review: true,
automatic_release: false,
api_key_path: "fastlane/AuthKey.json",
precheck_include_in_app_purchases: false,
force: true # skip the HTML preview prompt, which fails in non-interactive CI
)
commit_version_bump(message: "chore: release #{lane_context[SharedValues::VERSION_NUMBER]}")
add_git_tag
push_to_git_remote
end
# ---- ERROR HANDLING ----
error do |lane, exception|
# Send notification on failure (Slack, email, etc.)
# slack(
# message: "Lane #{lane} failed: #{exception.message}",
# success: false
# )
end
end
スクリーンショットの自動化を設定する
Fastlane Snapshot は、複数のデバイスと言語にわたる App Store 用スクリーンショットを自動的に撮影します。専用 Mac では、リソースを奪い合うことなく安定して実行できます。
# Initialize snapshot
fastlane snapshot init
# This creates:
# - fastlane/Snapfile
# - fastlane/SnapshotHelper.swift (add to UI test target)
Snapfile を設定する
# fastlane/Snapfile
# Simulator names from: xcrun simctl list devices available
devices([
"iPhone 17 Pro Max",
"iPhone 17",
"iPad Pro 13-inch (M5)"
])
languages([
"en-US",
"fr-FR",
"de-DE",
"ja"
])
scheme("MyAppUITests")
output_directory("./fastlane/screenshots")
clear_previous_screenshots(true)
# Speed up by running in parallel
concurrent_simulators(true)
UI テストに Snapshot を追加する
// In your XCUITest file:
import XCTest
class ScreenshotTests: XCTestCase {
override func setUp() {
continueAfterFailure = false
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
}
func testHomeScreen() {
snapshot("01_HomeScreen")
}
func testDetailScreen() {
let app = XCUIApplication()
app.cells.firstMatch.tap()
snapshot("02_DetailScreen")
}
func testSettings() {
let app = XCUIApplication()
app.tabBars.buttons["Settings"].tap()
snapshot("03_Settings")
}
}
スクリーンショット用のレーンを追加する
# Add to your Fastfile:
desc "Capture App Store screenshots"
lane :screenshots do
capture_screenshots
frame_screenshots(white: true) # Add device frames
upload_to_app_store(
skip_binary_upload: true,
skip_metadata: true,
api_key_path: "fastlane/AuthKey.json",
force: true # skip the HTML preview prompt when run in CI
)
end
TestFlight にデプロイする
CI 環境では、Apple ID の認証情報の代わりに App Store Connect API キーを使います。これにより、ヘッドレスサーバーでの 2FA プロンプトを回避できます。
API キーを作成する
- App Store Connect > Users and Access > Integrations > App Store Connect API に移動します
- + ボタンをクリックして新しい API キーを生成します
- App Manager ロールを選択します
.p8ファイルをダウンロードします- Key ID と Issuer ID を控えておきます
API キーの JSON を作成する
# fastlane/AuthKey.json
{
"key_id": "YOUR_KEY_ID",
"issuer_id": "YOUR_ISSUER_ID",
"key": "-----BEGIN PRIVATE KEY-----\nYOUR_P8_KEY_CONTENT\n-----END PRIVATE KEY-----",
"in_house": false
}
# IMPORTANT: Add this to .gitignore!
echo "fastlane/AuthKey.json" >> .gitignore
デプロイを実行する
# Deploy to TestFlight
fastlane beta
# Or run the full release pipeline
fastlane release
CI/CD と連携する
Fastlane はあらゆる CI/CD プラットフォームとシームレスに連携します。ここでは、セルフホストの Mac Mini M4 ランナーを使った GitHub Actions の例を示します。
# .github/workflows/deploy.yml
name: Deploy to TestFlight
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: [self-hosted, macOS, ARM64, M4]
# push_to_git_remote needs write access: the token is read-only by default
permissions:
contents: write
env:
# Xcode for this job, without sudo (path from `xcodes installed`)
DEVELOPER_DIR: /Applications/Xcode-27.0.0.app/Contents/Developer
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
# base64 of "github_username:personal_access_token", for example:
# echo -n your_github_username:your_personal_access_token | base64
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use the Homebrew Ruby (3.1 or later)
run: echo "/opt/homebrew/opt/ruby/bin" >> "$GITHUB_PATH"
- name: Set up App Store Connect API Key
run: |
mkdir -p fastlane
echo '${{ secrets.APP_STORE_CONNECT_API_KEY }}' > fastlane/AuthKey.json
- name: Install dependencies
run: |
bundle install
pod install # if using CocoaPods
- name: Deploy to TestFlight
run: bundle exec fastlane beta
- name: Clean up API key
if: always()
run: rm -f fastlane/AuthKey.json
ベストプラクティス
バージョンを固定した Fastlane を含む Gemfile を追加します。bundle exec fastlane を実行して、すべての環境で一貫したバージョンを保証します。
API キーは 2FA プロンプトを回避でき、CI 環境ではより安全です。
match --readonly を使う
新しい証明書が誤って作成されるのを防ぎます。証明書は必要なときにだけ手動で生成します。
setup_ci を使う
これにより一時的なキーチェーンが作成され、システムキーチェーンを汚さずに済み、キーチェーンの権限ダイアログを防げます。
専用 Mac では、derived_data_path を指定して、実行間でビルド成果物を再利用します。