ローカルでは通る fastlane が、GitHub Actions に載せた途端に固まる。署名で落ちる。bundle install すら通らない。原因の多くは、ランナーには対話する相手がいないことと、キーチェーンや Ruby の環境がローカルと違うことの2つに集約できます。この記事で扱うのは「fastlane とは何か」ではなく、GitHub Actions の macOS ランナー上で fastlane を最後まで走らせ、TestFlight へ届けることです。そのまま使えるワークフロー、署名と認証の渡し方、Appfile と Fastfile の書き方、詰まったときの見どころを、2026年10月時点の fastlane 2.241.1 とランナーイメージの実データで説明します。fastlane 自体の導入手順やアクションの一覧はfastlaneとは?iOS・Androidのビルドと配信を自動化する使い方【2.241・Ruby 3.2対応】にまとめています。
まとめ:GitHub Actions × fastlane 構成の要点
- ワークフローは checkout・setup-ruby・
bundle exec fastlaneの3段で足ります。処理は Fastfile に寄せ、YAML には Xcode の固定とタイムアウト、Secrets の受け渡しだけを書きます。 - Fastfile の先頭は
setup_ci。一時キーチェーンを作るアクションで、公式ガイドは入れない場合を「ビルドが固まって終わらない可能性がある」と書いています。 - 認証は App Store Connect API キーで行います。Apple ID でも
fastlane spaceauthで作ったセッションをFASTLANE_SESSIONで渡す方法はありますが、2要素認証に依存しない API キーのほうが CI では扱いやすいです。 - ビルド番号は CI で採番します。
latest_testflight_build_numberに1を足してincrement_build_numberへ渡せば、同じ番号の再アップロードで落ちる事故(ITMS-90189)を防ぎます。配信ジョブが同時に2本走らないよう、concurrencyで直列化しておくことが前提です。 - Ruby は 3.2 以上が必要です。fastlane 2.241.0(2026年10月10日)で必要 Ruby が 3.2 に上がりました。2026年5月の 2.235.0 から5か月で 2.7→3.0→3.1→3.2 と3段上がっています。
- Xcode とランナーのラベルは固定します。
macos-latestは現在 macOS 26 で、Xcode 26.0.1〜26.6 が同居し、既定は 26.6 です。Xcode 27 はまだプレビュー用のxcode-27ラベルでしか使えません。 - macOS ランナーの分単価は $0.062 です。Linux 2-core の約10倍です。プライベートリポジトリで無料枠を超えて使い続け、やることがビルドと配信だけなら、Xcode Cloud のほうが安く済みます。
GitHub Actionsでfastlaneを動かすワークフロー
公式サンプルYAMLの3ステップと読み方
fastlane 公式の GitHub Actions 統合ガイド(2026年10月16日ビルド)が示すワークフローは次のとおりです。
name: build-ios-app
on:
push:
branches:
- 'master'
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.4
- run: fastlane beta
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
runs-on は macOS が必須で、Linux ランナーには Xcode が無いため iOS のビルドはできません。ruby/setup-ruby は Ruby の版を固定する役割で、ランナーイメージの更新でプリインストールの Ruby が変わっても影響を受けなくなります。このサンプルは最小構成なので、実運用では3点を足します。bundler-cache: true で Gemfile.lock どおりの gem を入れてキャッシュすること、fastlane ではなく bundle exec fastlane で呼ぶこと、そして actions/checkout を現行の v7(v7.0.0 が2026年6月18日公開)に上げることです。ガイドはまだ v6 のままです。
ランナーには fastlane 自体もプリインストールされています。サンプルの run: fastlane beta が Gemfile 無しでも動くのはこのためですが、その版はイメージ次第で、10月13日の正式イメージは 2.241.0、10月23日に展開が始まったプレリリースのイメージは 2.241.1 でした。イメージが更新されるたびに fastlane の版も勝手に変わります。Gemfile.lock で版を固定し、bundle exec で呼んでください。
実運用向けのワークフロー全体例
手動実行とタグの push で TestFlight へ配信し、失敗時にログを回収する構成です。.github/workflows/ios-testflight.yml として置きます。前提として、Gemfile と Gemfile.lock をコミットし、証明書リポジトリの URL を書いた Matchfile を用意したうえで、ローカルで一度 match appstore を実行して証明書とプロファイルを作っておきます。CI 側の match は setup_ci によって readonly になり、新しい証明書を作らないためです。
name: ios-testflight
on:
workflow_dispatch:
push:
tags:
- 'v*'
permissions:
contents: read
concurrency:
group: ios-testflight
cancel-in-progress: false
jobs:
testflight:
runs-on: macos-26
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- run: sudo xcode-select -s /Applications/Xcode_26.6.app
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: bundle exec fastlane beta
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
- if: failure()
uses: actions/upload-artifact@v7
with:
name: fastlane-logs
path: |
fastlane/logs
fastlane/test_output
各行の意図は次の5点です。
runs-on: macos-26とxcode-select:macos-latestは GitHub の判断で中身が変わるため、OS と Xcode を両方固定します(理由は次の節)。concurrency:手動実行とタグの push が重なると、2本のジョブが TestFlight の同じ最新番号を読み、同じビルド番号で上げようとします。同じグループ名で直列化し、実行中のジョブは取り消さずに待たせます。同じアプリを配信するワークフローが他にもあるなら、グループ名を揃えてください。timeout-minutes: 45:キーチェーン待ちなどでジョブが止まると、既定では6時間まで課金が続きます。ふだんの所要時間の1.5〜2倍で切ってください。permissions: contents: read:配信だけのジョブにリポジトリへの書き込み権限は要りません。タグやコミットを push するレーンを作るときだけcontents: writeに上げます。- Secrets は
env:経由:ログに出た Secrets は GitHub が伏せ字にしますが、公式の説明はコマンドラインで渡すとpsなどで他のプロセスから見える可能性があるとして、環境変数での受け渡しを勧めています。fastlane 側はENV['ASC_KEY_ID']のように読みます。
ログを回収する fastlane/logs は、後述の build_app(buildlog_path: "./fastlane/logs") で出力先を指定したディレクトリです。既定の出力先はホームディレクトリ配下なので、指定しないとアーティファクトが空になります。ワークフロー全般の書き方はGitHub Actionsでビルド・自動テストを設定する方法|CI/CDワークフローの作り方で扱っています。
macos-latestランナーの実体とXcodeの固定
macos-latest の中身は、GitHub が公開している runner-images のリリースで確認できます。2026年10月13日公開の正式イメージ 20261013.0424.1 の内容は次のとおりです(10月23日の 20261023.0449.1 はプレリリースとして展開中で、OS と Xcode は同じです)。
| 項目 | 内容 |
|---|---|
| ラベル | macos-latest = macos-26(Arm64) |
| OS | macOS 26.6.2(25G83) |
| Ruby | 3.4.11(Bundler 4.0.22) |
| fastlane | 2.241.0(プリインストール) |
| Git | 2.56.0 |
| Xcode 既定 | 26.6(17F113) |
| 同居する Xcode | 26.0.1 / 26.1.1 / 26.2 / 26.3 / 26.4.1 / 26.5 |
| Intel が要る場合 | macos-26-intel |
7つの Xcode が同居していて、/Applications/Xcode.app がどれを指すかは GitHub が決めます。2026年7月にも既定が 26.5 から 26.6 へ入れ替わりました。-latest ラベル自体も、公式の方針で1〜2か月かけて新しい OS へ段階的に移ります。コードを変えていないのにビルドだけが落ちる事故はここから起きるため、ジョブの先頭で sudo xcode-select -s /Applications/Xcode_26.6.app を実行して版を固定します。
App Store Connect へのアップロードは、2026年4月28日から Xcode 26 以降・iOS 26 SDK でのビルドが必須です(Apple の Upcoming Requirements)。macos-26 に載る Xcode はどれもこの条件を満たすので、提出のために Xcode 27 へ急ぐ必要はありません。Xcode 27 は runs-on: xcode-27(または xcode-27-xlarge)の専用ラベルで試せますが、GitHub は「preview」扱いで「一部のソフトウェアが不安定な可能性がある」と断っています。このラベルの土台 OS は2026年9月16日に macOS 27 へ変わり、10月23日のイメージでは macOS 27.0.1・既定 Xcode 27.1 です。リリース用のレーンではなく、検証用のジョブで使ってください。Xcode 本体の版と更新手順はXcodeの最新バージョンは27|確認・アップデート手順とIntel Macは26.6までにあります。
もう1つ、ワークフローの中で openssl コマンドを使って .p12 を作ったり変換したりしている場合は、2026年11月2日〜8日に macOS 26 と xcode-27 のイメージで既定の openssl が 3.x から 4.x へ切り替わる予定です(runner-images issue #14856)。match だけで署名している構成には関係ありません。
Marketplaceのfastlane-actionとbundle exec直書きの選び方
「fastlane github」で検索すると、GitHub Marketplace の maierj/fastlane-action(v3.2.0・2026年6月11日)が出てきます。結論から言うと、新しく組むなら run: bundle exec fastlane <lane> を直接書くほうを勧めます。v3.2.0 の main.js は2,787バイトで、指定ディレクトリへ移動して bundle exec fastlane <lane> を実行するだけの薄いラッパーです。v3 からは Gemfile の依存もインストールしなくなり、README は ruby/setup-ruby に bundler-cache: true を付けるよう案内しています。アクションを挟んで得られるのは subdirectory と、レーン引数を JSON で渡す options くらいで、どちらも working-directory と通常の引数で代わりが効きます。
Flutter・React Nativeでのios配下のworking-directory指定
Flutter や React Native のリポジトリでは、fastlane のディレクトリはリポジトリ直下ではなく ios/ の下に置きます。Flutter の公式ドキュメントも [project]/ios で fastlane init を実行し、Gemfile も ios/ と android/ のそれぞれに置くよう案内しています。CI では先に flutter build ios --release --no-codesign --config-only で Xcode 側の設定を生成し、そのあと ios/ で bundle exec fastlane を実行する、という順番です。
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
working-directory: ios
- run: flutter build ios --release --no-codesign --config-only
- run: bundle exec fastlane beta
working-directory: ios
ruby/setup-ruby の working-directory を書き忘れると、リポジトリ直下に Gemfile が無いため bundler-cache が効きません。Flutter SDK の版をプロジェクトごとに固定する方法はFVMとは?Flutter SDKをプロジェクト単位で固定する運用とCI設定【2026年8月時点】で扱っています。
CIでの認証と署名:setup_ciとApp Store Connect APIキー
setup_ciが作る一時キーチェーンとmatchのreadonly化
CI で最初に呼ぶアクションが setup_ci です。公式ドキュメントは役割を3つ挙げています。match 用の一時キーチェーンを作ること、match を readonly モードにして CI から新しい証明書やプロファイルを発行させないこと、ログとテスト結果の出力先を回収しやすい場所に設定することです。
2.241.1 の実装では、setup_ci は空パスワードの一時キーチェーンを作ってロックを解除し、既定のキーチェーンかつ検索対象に加えます。match が取り込む証明書はここに入るため、codesign がパスワードを求めずに署名できます。公式ガイドが書くとおり、入れないとビルドが固まって終わらないことがあります。CI で原因不明のハングに当たったら、まずここを疑ってください。
| パラメータ | 既定値 | 用途 |
|---|---|---|
| force | false | CI 判定に関係なく実行する |
| provider | 自動検出 | CI を明示する。指定できるのは travis と circleci のみ |
| timeout | 3600(秒) | キーチェーンのタイムアウト。0 で無期限 |
| keychain_name | fastlane_tmp_keychain | 作成する一時キーチェーン名 |
| set_default_keychain | true | 一時キーチェーンを既定にする |
ソース(2.241.1 の setup_ci.rb)を読むと、ドキュメントには無い挙動が3つあります。1つ目は provider の検証で、travis と circleci 以外を弾くため、provider: "github" と書くと「サポートされていない CI プロバイダ」でエラーになります。GitHub Actions では指定を省きます。2つ目は、環境変数 MATCH_KEYCHAIN_NAME が既に設定されていると、キーチェーンは指定済みとみなして何も作らずに戻る点です。リポジトリ変数などでこの値を渡しているなら、一時キーチェーンが作られていないことを疑ってください。3つ目は出力先の設定で、これは後述の run_tests の節で扱います。
ビルドとアップロードを合わせて60分を超えるジョブでは、既定の3600秒でキーチェーンがロックされ、署名を伴う後半の処理が落ちます。長いジョブでは setup_ci(timeout: 0) にしてください。
App Store Connect APIキーによる非対話認証
Apple ID で認証する場合、CI では2要素認証のコードを入力できないため、ローカルで fastlane spaceauth を実行して作ったセッションを環境変数 FASTLANE_SESSION で渡す必要があります(公式の CI ガイド)。個人の Apple アカウントのセッションに依存する方式なので、CI では2要素認証に依存しない API キーを使うほうが運用が安定します。app_store_connect_api_key アクションで、App Store Connect で発行した .p8 形式のキーを渡します。
| パラメータ | 既定値 | 内容 |
|---|---|---|
| key_id | 必須 | キー ID |
| issuer_id | 省略可 | Issuer ID。個人用キーでは省略 |
| key_content | なし | .p8 の中身を直接渡す |
| key_filepath | なし | .p8 ファイルのパスを渡す |
| is_key_content_base64 | false | key_content が Base64 か |
| duration | 500(秒) | トークンの有効期間。上限1200 |
| in_house | false | Enterprise(社内配布)か |
結果は SharedValues::APP_STORE_CONNECT_API_KEY に入り、以降の match、latest_testflight_build_number、upload_to_testflight など App Store Connect と通信するアクションが自動で参照します。レーンの先頭で一度呼べば、各アクションに個別に渡す必要はありません。build_app は App Store Connect と通信しないため API キーは使わず、署名には match が入れた証明書とプロファイルを使います。duration は1200(20分)を超えるとエラーで止まります。トークンは期限が来ると fastlane が作り直すので、長いジョブでも上限まで伸ばす必要はありません。
GitHub Secretsに登録する値と改行の扱い
.p8 キーは複数行のテキストです。Secrets は複数行の値も保持できますが、シェルを経由する受け渡しで改行が壊れやすいため、Base64 で1行にしてから登録するほうが確実です。
base64 -i AuthKey_D83848D23.p8 | pbcopy
base64 -i は macOS 版の書式で、GNU coreutils では -i の意味が違います。Linux で作るなら base64 < AuthKey_D83848D23.p8 としてください。出力を ASC_KEY_CONTENT として登録し、Fastfile 側で is_key_content_base64: true を指定します。
match を併用するなら、証明書リポジトリの復号パスフレーズを MATCH_PASSWORD に、プライベートリポジトリへの認証情報を MATCH_GIT_BASIC_AUTHORIZATION に登録します。後者は ユーザー名:トークン を Base64 にした値です。fastlane 2.241.0 からは、この認証ヘッダーを git のコマンドライン引数ではなく環境変数(GIT_CONFIG_COUNT など)で渡すようになり、クローンした .git/config にも残らなくなりました(PR #30381)。その代わり git 2.31 以上が必要で、古い git では版を示して止まります。GitHub のランナーは git 2.56.0 なので問題になるのはセルフホストランナーだけです。
default_platform(:ios)
platform :ios do
desc "TestFlightへ配信する"
lane :beta do
setup_ci if ENV['CI']
app_store_connect_api_key(
key_id: ENV['ASC_KEY_ID'],
issuer_id: ENV['ASC_ISSUER_ID'],
key_content: ENV['ASC_KEY_CONTENT'],
is_key_content_base64: true
)
match(type: "appstore")
build_app(scheme: "MyApp", buildlog_path: "./fastlane/logs")
upload_to_testflight(skip_waiting_for_build_processing: true)
end
end
証明書とプロビジョニングプロファイルの仕組みでつまずいているなら、プロビジョニングプロファイルとは|iOS署名の仕組み・4種類・作成・期限・エラー対処で先に構造を押さえたほうが早く解決します。
Appfileに書く項目とチームIDの使い分け
app_identifier・apple_id・team_id・itc_team_idの役割
Appfile は ./fastlane ディレクトリに置く設定ファイルで、アプリと組織を識別する値だけを持ちます。混乱しやすいのは「Apple ID が2種類」「チーム ID が2種類」ある点です。Developer Portal と App Store Connect が別システムだった名残で、アカウントを分けている組織ではそれぞれ違う値になります。
| キー | 指す値 |
|---|---|
| app_identifier | アプリのバンドル ID |
| apple_id | Apple アカウントのメールアドレス |
| apple_dev_portal_id | Developer Portal 用アカウント(分ける場合) |
| itunes_connect_id | App Store Connect 用アカウント(同上) |
| team_id | Developer Portal の Team ID(英数10桁) |
| team_name | Developer Portal のチーム名 |
| itc_team_id | App Store Connect の Team ID(数値) |
| itc_team_name | App Store Connect のチーム名 |
itc_team_id が要るのは、1つの Apple アカウントが複数のチームに所属していて、Apple ID でログインする場合です。未指定だと fastlane がどのチームで実行するかを対話で尋ね、CI はそこで止まります。API キーだけで完結する構成ならトークンが発行元のチームに紐づくため、この選択は起きません。
for_platform / for_laneによる環境別の値の切り替え
開発用と本番用でバンドル ID を分けている場合は、Appfile でレーンごとに値を切り替えます。
app_identifier "com.example.myapp"
apple_id "[email protected]"
team_id "ABCDE12345"
itc_team_id "123456789"
for_platform :ios do
for_lane :test do
app_identifier "com.example.myapp.dev"
end
end
優先順位は公式ドキュメントに明記されていて、レーン固有の値があればそれを使い、無ければファイル冒頭の値に戻ります。Fastfile から値を読むときは CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier) を使います。
Fastfileのlane設計:テスト・ビルド・採番・配信
run_testsによるテスト実行と結果ファイルの回収
run_tests は xcodebuild test のラッパーです。CI では対象のシミュレータを固定することと、テスト結果の出力先を指定することが要点になります。
lane :test do
run_tests(
scheme: "MyApp",
devices: ["iPhone 17"],
result_bundle: true,
output_directory: "./fastlane/test_output"
)
end
devices を書かないと、イメージに入っているシミュレータの並び順に依存し、イメージ更新でテスト対象の端末が変わります。result_bundle: true を付ければ .xcresult が残り、actions/upload-artifact でそのまま回収できます。
output_directory は省かないでください。setup_ci の説明には「ログとテスト結果のパスを回収しやすい場所に設定する」とありますが、ソースではこの処理はプロバイダが CircleCI か CodeBuild のときにしか呼ばれず、さらに環境変数 FL_OUTPUT_DIR が無ければ何もしません。GitHub Actions では動かないため、setup_ci を入れたのに .xcresult が想定の場所に無く、アーティファクトが空になります。
build_appのIPA生成とexport_methodの整合
build_app はビルドとアーカイブ、IPA の書き出しまでを担います。旧名の gym はエイリアスとして残っていて、ソースでは gym・build_ios_app・build_mac_app が別名として定義されています。古い記事のサンプルが gym で書かれていてもそのまま動きます。
build_app(
scheme: "MyApp",
export_method: "app-store",
output_directory: "./build",
buildlog_path: "./fastlane/logs",
clean: true
)
export_method がとれる値は8種類(app-store、validation、ad-hoc、package、enterprise、development、developer-id、mac-application)で、iOS の配布で使うのは app-store、ad-hoc、enterprise、development の4つです。match で取得したプロファイルの種別と一致しないと書き出しの段階で落ちるため、match(type: "appstore") なら export_method: "app-store" に揃えます。
TestFlightの最新値によるビルド番号の自動採番
CI で配信を回し始めると、最初に当たる失敗がビルド番号の重複です。同じバージョンに同じビルド番号の IPA を上げると、App Store Connect は次のエラーで受け付けません。
ERROR ITMS-90189: Redundant Binary Upload. You've already uploaded a build with build number '124' for version number '0.18.3'.
ビルド番号を手で上げる運用だと、上げ忘れたコミットがそのまま配信に乗ります。TestFlight にある最新の番号を引き、1を足して書き込むレーンにしてください。
lane :beta do
setup_ci if ENV['CI']
app_store_connect_api_key(
key_id: ENV['ASC_KEY_ID'],
issuer_id: ENV['ASC_ISSUER_ID'],
key_content: ENV['ASC_KEY_CONTENT'],
is_key_content_base64: true
)
current = latest_testflight_build_number(initial_build_number: 0)
increment_build_number(build_number: current + 1)
match(type: "appstore")
build_app(scheme: "MyApp", export_method: "app-store", buildlog_path: "./fastlane/logs")
upload_to_testflight(skip_waiting_for_build_processing: true)
end
version を渡さない latest_testflight_build_number は、バージョンを問わず最後にアップロードされたビルドの番号を整数で返します。2.241.1 のソースでは、処理中・失敗・完了のアップロードを日時の新しい順に1件だけ引く実装です。つまりこのレーンは、ビルド番号をバージョンをまたいだ通し番号で増やします。アプリにまだ1件もビルドが無いときは initial_build_number(既定は1)を返すので、0 にしておけば最初が1になります。バージョンごとに1から振り直したいなら、version: に対象のバージョン文字列を渡してください。increment_build_number は build_number を明示すれば、Xcode プロジェクトで Apple Generic Versioning を有効にしていなくても動きます。
GitHub Actions の github.run_number をビルド番号に使う方法もよく紹介されますが、勧めません。公式の説明どおり、この番号は「ワークフローを再実行しても変わらない」ため、アップロードまで成功したあとで後続のステップが落ちて再実行すると、同じ番号で上げ直して ITMS-90189 になります。TestFlight の値を基準にすれば、前の試行でアップロードまで済んでいても、再実行では番号が1つ進みます。
upload_to_testflight・upload_to_app_storeによる配信先の使い分け
配信先でアクションが変わります。TestFlight への配信は upload_to_testflight、App Store への申請は upload_to_app_store(エイリアスは deliver と appstore)です。
CI で upload_to_testflight を使うなら skip_waiting_for_build_processing: true を付けてください。付けないと Apple 側の処理が終わるまでジョブが待ち続け、その時間も macOS ランナーの課金対象になります。ただし、この指定をすると distribute_external は効かず、テスターへの配布も行われません。changelog を渡した場合は、変更履歴を設定できるところまでは待ちます。アップロード直後にテスターへ自動配布したいなら、待機を残してください。
metadataとscreenshotsによるストア情報の自動反映
metadataディレクトリのファイル構成とリリースノートの置き場所
upload_to_app_store は、決められたディレクトリ構成のテキストファイルと画像を読んで App Store Connect に反映します。ソースの実装では、ローカライズする値を <metadata_path>/<言語>/<キー名>.txt から、言語に依存しない値を <metadata_path>/<キー名>.txt から読み込みます。
fastlane/
Appfile
Fastfile
metadata/
copyright.txt
primary_category.txt
ja/
description.txt
keywords.txt
release_notes.txt
en-US/
description.txt
keywords.txt
release_notes.txt
screenshots/
ja/
en-US/
言語ごとに置けるキーは description、keywords、release_notes、support_url、marketing_url、promotional_text、name、subtitle、privacy_url です。リリースノートのファイル名は release_notes.txt で、App Store Connect の API 側では whats_new と呼ばれる項目に入ります。copyright と primary_category は言語に依存しないため metadata 直下に置きます。
CIでforce: trueとskip_screenshotsを指定する理由
CI で必ず指定するのが force: true です。説明は「HTML プレビューファイルの検証をスキップする」で、指定しないと deliver は反映内容の HTML プレビューを作って確認を求めます。CI では fastlane が非対話モードで動くため、この確認の場面で Could not retrieve response as fastlane runs in non-interactive mode というエラーになり、ジョブが落ちます。
upload_to_app_store(
force: true,
skip_screenshots: true,
submit_for_review: false
)
スクリーンショットは端末サイズの数だけ画像を転送するため、毎回のビルドで走らせると時間と課金の両方がかさみます。スクリーンショットを更新する専用のレーンを分け、通常のリリースレーンでは skip_screenshots: true にしてください。
CI固有のエラーと切り分け手順
multi_jsonのGem::LoadError(googleauth 1.17.0 × fastlane 2.236.0未満)
2026年半ばに増えたのが、次のエラーで fastlane が起動時に落ちるパターンです。
multi_json is not part of the bundle. Add it to your Gemfile. (Gem::LoadError)
bundle install 自体は成功し、落ちるのは require の瞬間、つまり fastlane を起動したときです。原因は依存先の googleauth v1.17.0 が multi_json を依存から外したことでした。fastlane の PR #30062 は「Ruby 3.3 以降のテストで全ビルドが失敗している」と説明し、multi_json を fastlane 自身の直接依存に加えています。
RubyGems の依存情報では、2.235.0(2026年5月26日)の runtime dependencies に multi_json は無く、2.236.0(2026年6月8日)から multi_json ~> 1.12 が入りました。切り分けには Gemfile.lock を見るのが確実です。fastlane が 2.236.0 未満、googleauth が 1.17.0、multi_json が lock に無い、の3つがそろっていればこのエラーの条件に当たります。PR の報告は Ruby 3.3 以降のテストでの失敗で、手元とランナーで Ruby の版や lock の中身が違うと、片方だけで落ちることがあります。
bundle update fastlane
bundle exec fastlane --version
2.236.0 以上に上げれば解消します。上げられない事情があるなら Gemfile に gem 'multi_json' を足すのが暫定策です。
Ruby要件エラー:2.235.0・2.239.0・2.241.0の引き上げ
fastlane の必要 Ruby は、この半年で3回上がっています。RubyGems の版ごとの記録(required_ruby_version)で境界を確認すると次のとおりです。
| 版 | 公開日 | 必要 Ruby |
|---|---|---|
| 2.234.0 | 2026-05-10 | 2.7 以上 |
| 2.235.0〜2.238.0 | 2026-05-26〜08-12 | 3.0 以上 |
| 2.239.0〜2.240.1 | 2026-09-04〜09-15 | 3.1 以上 |
| 2.241.0〜2.241.1 | 2026-10-10〜10-15 | 3.2 以上 |
Gemfile.lock を更新せずに古い Ruby で bundle update すると、Bundler は Ruby 要件を満たす最後の版で止まるか、要件エラーで落ちます。ランナー側は ruby/setup-ruby で 3.4 を入れるので通り、手元だけ落ちる、という逆パターンも起きます。macOS 26 に同梱の /usr/bin/ruby は 2.6.10 でどの版の要件も満たさないため、rbenv や Homebrew で 3.2 以上を用意してください。Ruby の版管理はrbenvとは?Rubyバージョン管理の仕組みとRVMとの違い・1.3系の導入手順で扱っています。各版の変更点は fastlane の Releases で確認できます。
CIで止まる・落ちるときの切り分け表
CI で fastlane が先へ進まなくなる原因は2種類に分かれます。1つは fastlane 自身の確認入力で、GitHub Actions では環境変数 GITHUB_ACTIONS などを見て非対話モードになるため、確認や選択の場面は待たずに Could not retrieve response as fastlane runs in non-interactive mode で落ちます。もう1つはキーチェーンのように fastlane の外のプロセスが入力を待つ場合で、こちらはエラーが出ないまま止まります。止まっている間も macOS ランナーの $0.062/分は課金され、GitHub ホストランナーのジョブ上限は6時間なので、放置すれば1回で最大約22ドルになります。最初に確かめる箇所は次のとおりです。
| 止まる場所 | 待っているもの | 対処 |
|---|---|---|
| match / 署名 | キーチェーンのロック解除 | レーン先頭で setup_ci |
| upload_to_app_store | HTML プレビューの確認 | force: true |
| Apple へのログイン | 2要素認証のコード | app_store_connect_api_key |
| チーム選択 | 所属チームの選択 | Appfile に team_id・itc_team_id |
| ジョブ後半の署名 | 期限切れのキーチェーン | setup_ci(timeout: 0) |
表は当たりを付けるための初期確認用です。ジョブログの最後に動いていたアクションを見て当てはめ、どれにも当たらなければ bundle exec fastlane beta --verbose で詳細ログを出し、最後に呼ばれていた外部コマンド(xcodebuild、codesign、git など)を確かめてください。
macOSランナーの費用と、この構成を選ぶべきでない場面
macOSランナーの分単価とXcode Cloudとの時間単価比較
GitHub Actions の標準ランナーは OS で単価が大きく違います(GitHub の料金表)。macOS 3-core または 4-core が1分 $0.062、Linux 2-core(x64)が $0.006、Windows 2-core が $0.010 で、macOS は Linux の約10倍です。ジョブごとに1分未満は切り上げられます。標準ランナーはパブリックリポジトリでは無料で、プランに含まれる分数(GitHub Free 2,000分、Pro と Team が3,000分、Enterprise Cloud が50,000分)はプライベートリポジトリ向けです。M2 Pro の macos-26-xlarge($0.102/分)などの larger runner は含有分数を使えず、パブリックリポジトリでも課金されます。
金額にすると、ビルドとテストで15分かかるパイプラインは1回約 $0.93、1日5回・月20営業日の100回で約 $93 です(無料枠を差し引く前の金額)。ここに match のクローンや TestFlight のアップロード待ちが加わります。料金の全体像とコストを抑える方法はGitHub Actionsの料金|2026年改定後の分単価・無料枠とコスト削減の判断基準にまとめています。
この単価を Xcode Cloud と並べると判断が変わります。Xcode Cloud は Apple Developer Program に月25コンピュートアワーが含まれ、それを超えて使うなら月100時間で US$49.99 のプランに切り替えます(25時間に100時間が上乗せされるわけではありません)。100時間を使い切った場合の1時間あたりは約 $0.50 で、GitHub Actions の macOS ランナーを1時間動かす $3.72($0.062 × 60)の約7分の1です。コンピュートアワーとジョブの経過時間は同じ作業でも一致するとは限らないため、目安としての比較です。
GitHub Actions + fastlaneを選ぶべきでない条件と、選ぶ理由
プライベートリポジトリで無料枠を超えて macOS ランナーを使い続け、やることがビルドと TestFlight 配信だけなら、GitHub Actions + fastlane は選ばないほうがいい。時間単価では Xcode Cloud が数倍安いうえ、この構成では証明書を match で自前管理する作業が残ります。Xcode Cloud なら署名は Apple 側が持つので、この記事の前半で扱ったキーチェーンと API キーの設定がまるごと要りません。
逆に、次のどれかに当てはまるならこの構成に投資する価値があります。
- Android と同じパイプラインに載せたい。fastlane は
upload_to_play_storeを持つので、iOS と Android を同じ仕組みで扱えます。Android のジョブは安い Linux ランナーに回せます。 - ビルド以外のジョブを同居させたい。Lint、依存の脆弱性チェック、リリースノートの自動生成、Slack 通知などを同じワークフローに書けます。
- リポジトリのイベントを起点にしたい。特定のラベルが付いた PR だけ配信する、タグの push で本番申請する、といった条件分岐は GitHub Actions のほうが自由に書けます。
判断の基準は「iOS のビルドが目的か、リリース工程全体の自動化が目的か」です。前者で有料の利用が続くなら Xcode Cloud、後者ならこの構成を選びます。社内のリリース工程をどこまで自動化するかの設計から相談したい場合は、DevOps・CI/CD導入支援でパイプラインの設計と構築を支援しています。
よくある質問
fastlaneとは何をするツールですか?
iOS と Android アプリのビルド、テスト、署名、ストアへの配信を自動化する Ruby 製のオープンソースツールです。実体は多数の「アクション」の集まりで、Fastfile に処理の並びを書いて実行します。導入手順やアクションの一覧はfastlaneとは?iOS・Androidのビルドと配信を自動化する使い方【2.241・Ruby 3.2対応】にまとめています。
Fastlane.swift(fastlane init swift)で書いたほうがよいですか?
CI で使うなら Ruby の Fastfile を勧めます。公式ドキュメントは Fastlane.swift を今も「ベータ版」と明記しています。Swift の Fastfile は FastlaneSwiftRunner という Xcode プロジェクトをビルドしてから実行されるため、Ruby 版に無い工程が挟まり、分単価の高い macOS ランナーではその時間がそのまま課金に効きます。
fastlaneからGitのタグ付けやコミットはできますか?
できます。add_git_tag がタグを作るアクションで、tag で文字列を直接指定するほか、grouping、prefix、build_number、postfix を組み合わせた自動生成もできます。リモートへの反映は push_to_git_remote です。CI から push するなら、ワークフローの permissions を contents: write にしてください。
fastlane supply はiOSでも使いますか?
使いません。supply は upload_to_play_store のエイリアスで、Google Play 向けのアクションです。iOS の App Store に送るのは upload_to_app_store(エイリアスは deliver)です。どちらもメタデータとスクリーンショットを扱うため、取り違えやすい組み合わせです。
Linuxランナーでもfastlaneは動きますか?
Android のレーンなら動きます。ubuntu-latest で Ruby と Android SDK を用意すれば、gradle でのビルドや upload_to_play_store での配信まで Linux で完結し、分単価は macOS の約10分の1で済みます。iOS のビルドと署名は Xcode が要るので macOS ランナーが必須です。iOS と Android を1つのワークフローに入れるなら、ジョブを分けて Android だけ Linux に回すと費用を抑えられます。