前の記事で EAS Cloud から TestFlight に上げる流れは整いましたが、EAS のビルド枠を気にする場面もあります。
そこで Xcode Cloud を試しました。Apple Developer Program に含まれる月 25 時間の無料枠があり、個人開発ではコスト面でありがたいですし、個人的にもこのフローを採用しております。
Expo アプリを Xcode Cloud で動かすには、ビルド環境に必要なものがないので、ci_post_clone.sh で準備する 必要があります。
手順は template-expo-build-cicd をベースに試した内容です。
サンプルリポジトリ
前の記事と同じテンプレートです。
eas.json や app.json の設定が入っているので、Bundle ID などを自分のものに差し替えて使います。
git clone https://github.com/testkun08080/template-expo-build-cicd.git
cd template-expo-build-cicd
npm ciテンプレートは Node.js 20.x 前提です(eas.json の base.node も 20.18.0)。
Xcode Cloud の ci_post_clone.sh でも、入っていなければ Homebrew で Node 20 を入れます。
clone 後に差し替える項目
| ファイル | 差し替える項目 |
|---|---|
app.json | ios.bundleIdentifier / extra.eas.projectId |
ios/templateexpobuild.xcodeproj/project.pbxproj | DEVELOPMENT_TEAM / PRODUCT_BUNDLE_IDENTIFIER |
extra.eas.projectId は Xcode Cloud では直接は使いませんが、ローカルで expo prebuild するときに eas init 済みのプロジェクトのほうが楽です。
前の記事の「clone 後に差し替える項目」もあわせて確認してください。
EAS Cloud と Xcode Cloud の使い分け
前の記事で触れた EAS Cloud に対して、Xcode Cloud 視点で整理します。
| EAS Cloud | Xcode Cloud | |
|---|---|---|
| Mac 必須 | 不要 | 不要(Apple 管理) |
| 署名 | EAS リモート credentials | Apple 自動(match 不要) |
| Expo prebuild | EAS 内蔵 | ci_post_clone.sh で実行 |
| 主なコスト | EAS ビルド枠 | 月 25h 無料枠 |
| セットアップ | eas.json + eas login | ios/ 部分コミット + CI スクリプト |
普段は EAS Cloud無料分をテスト的にビルドするのに使用し、ios向けだけならXcode Cloudを使っています。
前の記事で済ませていること
Xcode Cloud に進む前に、次が終わっているとスムーズです。
EAS Localと EAS Cloudで一通りやっているので、ここでは要点だけ。
- App Store Connect にアプリ登録済み(Bundle ID 確定)
app.jsonのios.bundleIdentifierがcom.testkun08080.template-expo-buildのように確定している- TestFlight まで一度上げた経験がある(提出フローのイメージがついている)
Xcode Cloud では EAS や EXPO_TOKEN は不要 です。
代わりに ios/ の最小コミットと ci_post_clone.sh の用意が必要になります。
Xcode Cloud とは
Xcode Cloud は Apple が提供する CI/CD サービスで、App Store Connect や Xcode から設定できます。
Archive から TestFlight 配布までを Apple 側で完結できるので、EAS を使わない配布経路として選択肢になります。
ワークフローを作る前に、手元の Mac で一度 Archive を通しておく必要があります。
次のセクションで、その手順を写真つきでまとめます。
ios/ の部分コミット戦略
Managed Workflow の Expo アプリでは、通常 ios/ は gitignore します。
Xcode Cloud では Xcode プロジェクトと CI スクリプトだけ をコミットする「部分コミット」が現実的です。
テンプレートでは expo prebuild 後の構成を、だいたい次のように整理しています。
ios/
├── ci_scripts/
│ └── ci_post_clone.sh # コミット
├── templateexpobuild.xcodeproj/ # pbxproj + scheme のみコミット
├── templateexpobuild.xcworkspace/ # コミット
└── (Podfile, Pods 等は gitignore → prebuild で生成)ios/.gitignore の例:
# 生成物は無視。Xcode プロジェクトと ci_scripts だけ残す
*
!.gitignore
!templateexpobuild.xcodeproj/
!templateexpobuild.xcworkspace/
!ci_scripts/
templateexpobuild.xcodeproj/*
!templateexpobuild.xcodeproj/project.pbxproj
!templateexpobuild.xcodeproj/project.xcworkspace/
!templateexpobuild.xcodeproj/xcshareddata/expo prebuild --clean のたびに Podfile や entitlements が再生成されるので、全部コミットするよりこの最小構成のほうが扱いやすいです。
scheme ファイルも xcshareddata/xcschemes/ に入っていることを確認して、明示的に git に含めてください。
ci_post_clone.sh
クローン直後に Node と native プロジェクトを用意します。
Xcode Cloud が認識するのは ios/ci_scripts/ci_post_clone.sh です。
テンプレートで実際に使っているのがこちらです(ci_post_clone.sh)。
#!/bin/sh
set -euo pipefail
cd "$CI_PRIMARY_REPOSITORY_PATH"
# Xcode Cloud images do not include Node.js by default.
if ! command -v node >/dev/null 2>&1; then
brew install node@20
brew link node@20 --force --overwrite
fi
npm ci
npx expo prebuild --platform ios
cd ios
pod installやっていることはだいたい次のとおりです。
CI_PRIMARY_REPOSITORY_PATH(Xcode Cloud が渡すリポジトリルート)へ移動- Node がなければ Homebrew で
node@20を入れる npm ci→expo prebuild→pod install
Flutter × Xcode Cloud の記事でも同じく CI_PRIMARY_REPOSITORY_PATH を使っています。
CocoaPods がイメージに無い場合は、Node の直後に brew install cocoapods を足すと安定しやすいです。
prebuild のキャッシュ不整合で何度も落ちるときは、npx expo prebuild --platform ios --non-interactive --clean と pod install --repo-update に切り替えてもよいです。
NODE_BINARY が必要なとき
React Native のビルドフェーズで Node が見つからないエラーが出る場合は、ci_post_clone.sh の末尾に次を追加します。
NODE_BINARY="$(command -v node)"
echo "export NODE_BINARY=${NODE_BINARY}" > ios/.xcode.env.localios/.xcode.env.local は通常 gitignore 対象なので、毎ビルドで生成する形にします。
ローカルで Archive を作る
Xcode Cloud のワークフローを設定する前に、手元の Mac で一度 Archive を通しておきます。
expo prebuild と pod install が済んだあと、ios/templateexpobuild.xcworkspace を Xcode で開いてください。
ワークスペースを開いて署名を確認する
左のナビゲータでプロジェクトを選び、Signing & Capabilities タブを開きます。
Archive 用の Release 構成も見ておくと安心です。
次の項目が意図どおりか確認します。
- Automatically manage signing がオンになっている
- Team が自分の Apple Developer アカウントになっている
- Bundle Identifier が
com.testkun08080.template-expo-buildで、app.jsonと一致している

project.pbxproj を直接編集している場合も、Xcode 上の表示と食い違っていないか見ておくと安心です。
部分コミット戦略では、だいたい次の値を入れています。
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = XXXXXXXXXX;
PRODUCT_BUNDLE_IDENTIFIER = com.testkun08080.template-expo-build;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;Xcode Cloud は App Store Connect と連携しているため、証明書の手動管理や fastlane match は不要です。
EAS Cloud の記事で整えた Bundle ID をそのまま使えます。
Archive を作成する
デスティネーションは実機または Any iOS Device を選び、メニューから Product → Archive を実行します。

Archive 完了後の Organizer
ビルドが通ると Organizer が開き、アーカイブが一覧に載ります。
テンプレートでは templateexpobuild という名前で、Version 1.0.0 (1) のように表示されます。

Identifier に com.testkun08080.template-expo-build、Team に自分のアカウント名が出ていれば、署名まわりはだいたい問題なさそうです。
TestFlight に手動で上げる場合は Distribute App から提出できますが、Xcode Cloud を使う場合は次のワークフロー設定に進みます。
ここまで通れば、Integrate → Create Workflow… のメニューも有効になることが多いです。
Xcode Cloud ワークフローを作る
Archive が通ったら、Xcode Cloud のワークフローを作成します。
メニューは Integrate → Create Workflow… から開けます(Product → Xcode Cloud からでも同じです)。

プロダクトを選ぶ
ワークスペース内のアプリ一覧が出るので、templateexpobuild を選びます。
Team 列に自分のアカウントが入っているかもここで確認できます。

ソースコードへのアクセスを許可する
GitHub リポジトリへのアクセスを求められたら、対象リポジトリ(testkun08080/template-expo-build-cicd など)にチェックを入れて進めます。

初回は GitHub アカウントと Apple ID の連携が必要なことがあります。
Xcode の Settings → Accounts で GitHub が登録されているかも見ておくとよいです。
App Store Connect のアプリと紐づける
App Store Connect 上のアプリ名と Bundle ID が表示されます。
com.testkun08080.template-expo-build になっているか確認して Next を押します。

ワークフロー内容を確認する
初期設定では、だいたい次のような内容になります。
| 項目 | 設定値 |
|---|---|
| 開始条件 | Branch Changes(main) |
| 環境 | Latest Release(Xcode の最新版) |
| アクション | Archive(iOS) |
| ポストアクション | TestFlight なし(初回はこうなることも) |

スキームは templateexpobuild、ワークスペースは templateexpobuild.xcworkspace を指定します。
初回は Post-Actions に TestFlight が載っていないこともあります(スクショ参照)。
実際にこの時点でワークフローを編集してもTestFlightの項目が出ない時がありました
初回ビルドを開始する
設定が終わると Start Build の画面が出ます。
main ブランチを選んで Start Build を押すと、Xcode Cloud 上で最初のビルドが走ります。

この時スタートしても閉じないので(僕の環境では)、、、
ボタンを押したら、以下の項目でビルドの動作を確認してから、閉じてしまってください。
ビルドの動作を確認する
ビルドが始まると、App Store Connect の Xcode Cloud タブでも進捗を追えます。
最初はステータスがくるくる回っている状態になります。

完了すると緑のチェックマークが付き、ビルド番号(1、2…)が並びます。
警告数は出ますが、Archive まで通っていれば TestFlight 配布の対象になります。

TestFlight タブ側でもビルドが載るまで少しラグがあることがあります。
Xcode Cloud のログで ci_post_clone.sh が通っているかもあわせて確認すると安心です。
TestFlight 配布を有効にする
1回ビルドが通ったあと、ワークフローの Archive - iOS アクションを開き、Distribution Preparation で TestFlight(Internal Testing Only) を選びます。

初回のワークフロー作成ウィザードでは TestFlight の項目が出てこないことがあります。
そのときは次の「TestFlight がワークフロー作成時に見えない」を試してから、Edit Workflow… で設定し直してください。
よくあるトラブル
TestFlight がワークフロー作成時に見えない
初回セットアップのウィザードで TestFlight の選択肢が出てこないことがあります。
僕の環境では、次の順で解消しました。
- 一度 Start Build で Xcode Cloud 上のビルドが走るのを確認する(App Store Connect の Xcode Cloud タブで OK)
- Xcode を再起動 する
- Integrate → Manage Workflows…(またはワークフロー編集)を開き直す
- Archive - iOS の Distribution Preparation に TestFlight が出るか確認する
うまくいかないときは、先に Archive だけのワークフローで1回通してから TestFlight を足す形でも進められます。
prebuild 後の Pod エラー
pod install --repo-update を ci_post_clone.sh に入れておくと、キャッシュ不整合を減らせます。
entitlements(App Groups など)は app.json に書き、prebuild で反映させます。
スキームが見つからない
expo prebuild 後のプロジェクト名と、Xcode Cloud で選ぶスキーム名が一致しているか確認してください。
部分コミット戦略では、templateexpobuild.xcodeproj/xcshareddata/xcschemes/ 以下の scheme ファイルを明示的に git に含める必要があります。
ビルド時間
毎回 npm ci と expo prebuild --clean が走るため、初回に近い時間がかかることがあります。
キャッシュは Xcode Cloud 側に任せつつ、スクリプトは必要最小限に保つのがよいです。
EAS は通るが Xcode Cloud だけ落ちる
EAS Cloudが通るなら Bundle ID や entitlements の設定自体は正しいことが多いです。
Xcode Cloud 側は ci_post_clone.sh のログ、NODE_BINARY、pod install の成否を順に見るのが良いと思います。
まとめ
- Xcode Cloud では
ci_post_clone.shで Node +expo prebuild+pod installが必須 ios/は Xcode プロジェクトとci_scriptsだけコミットする部分戦略が扱いやすい- ローカルで Archive を一度通してからワークフローを作ると設定がスムーズ
- TestFlight が見えないときは、Xcode Cloud で1回ビルド → Xcode 再起動を試す
- コスト面では月 25 時間無料枠が個人開発向き
次の記事では、前回の記事でやっていたビルドとテストフライトまでの提出フローを GitHub Actions 上で行いたいと思います。
fastlane match 版と、EAS Cloudを使用したものです。
→ Expo × GitHub Actions で TestFlight 自動デプロイ(fastlane / EAS)