0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

iOSアプリ(個人)開発を止めるな!Mac無し・$0・EAS Build枠切れでもリリースできる完全ガイド

0
Posted at

Gemini_Generated_Image_p9w5zcp9w5zcp9w5.png

TL;DR

  • 問題: Expo の EAS Build 無料枠(iOS 月15ビルド)をリリース直前に使い切ってしまい、App Store に提出できなくなった
  • 解決: GitHub Actions の macOS ランナーで expo prebuildxcodebuild archive を実行して IPA を生成し、アップロードは eas submit に任せた
  • コスト: 無料(Expo 有料プラン $19/月 の回避に成功)
  • 対象読者: Mac を持たずに Expo で iOS アプリを開発している個人開発者、EAS Build 枠に悩む人、がんがん個人開発したい人

AI駆動開発でスピードアップしても、App Storeへ申請を待たないといけないなら意味ないですよね💦


前提条件

この記事の解決策は、以下の開発環境・制約の下で実施したものです。似た状況の方の参考になれば幸いです。

開発環境

  • OS: WSL2 (Ubuntu 22.04) — Mac は所有していない
  • Node.js: v20
  • Package manager: npm
  • Git ホスティング: GitHub (Private リポジトリ)
  • CLI ツール: gh (GitHub CLI), eas-cli
  • エディタ: VS Code + Claude Code

アプリ構成

  • Expo SDK: 52(React Native 0.76 / New Architecture 有効)
  • 言語: TypeScript
  • フレームワーク: Expo managed workflow + expo-router
  • 主要ライブラリ: Clerk(認証), RevenueCat(IAP), expo-notifications(プッシュ通知)
  • ターゲット: iOS のみ(Android は後回し)

Apple 側の準備(前提)

  • Apple Developer Program に登録済み(個人アカウント)
  • App Store Connect にアプリ登録済み
  • Distribution Certificate(.p12)と Provisioning Profile(.mobileprovision)は EAS で発行済み
  • App Store Connect API Key(.p8)も発行済み

Expo 側の準備(前提)

  • Expo アカウント登録済みhttps://expo.dev無料プランで OK
  • eas-cli インストール済み: npm install -g eas-cli
  • eas login で認証済み
  • プロジェクトが eas init 済み(eas.json が存在する)
  • eas credentials で iOS の証明書・プロファイル発行済み

この解決策でも Expo 無料アカウントは必要 です。ただし 有料プラン($19/月)は不要。使うのは eas credentials(証明書管理)と eas submit(アップロード)のみで、これらはビルド枠とは別料金体系で無料です。詳しくは後述のコラムで説明します。

制約

この解決策を必要とした「制約」は以下の3点です:

  1. Mac を持っていない

    • ローカルで xcodebuild を直接実行できない
    • これまでビルドは全て EAS Build のクラウド Mac に頼っていた
  2. EAS Build 無料枠の枯渇

    • 無料プランでは iOS 月15ビルドまで(※本記事執筆時点)
    • リリース前の詰めの段階で試行錯誤を重ねた結果、月末を待たずに枯渇
    • リリース直前だったので「来月まで待つ」の選択肢が事実上なかった(というか待ってられない)
  3. 有料プランにコストをかけたくない

    • Expo Production プラン: $19/月(年額 $228)
    • 個人開発の小規模アプリでは正直重い
    • 一時的な枠切れのために継続課金するのは避けたい

何が起きたか

Gemini_Generated_Image_aeuyd7aeuyd7aeuy.png

リリース直前、eas build を実行した際に以下のエラーが出ました。

Compressing project files and uploading to EAS Build. Learn more:
https://expo.fyi/eas-build-archive
✔ Uploaded to EAS 11s
✔ Computed project fingerprint
This account has used its iOS builds from the Free plan this month, 
which will reset in 20 days (on Fri May 01 2026). 
Upgrade your plan for more builds with shorter wait times...
Error: build command failed.

「あと20日待て、または課金しろ」という状況。リリース日が迫っていたので、両方拒否したい…という状態で代替策を模索。


重要な洞察: EAS Build と EAS Submit は別物

Gemini_Generated_Image_szoao7szoao7szoa.png

解決策の核心はここにあります。

サービス 役割 無料枠制限
EAS Build クラウド Mac で React Native プロジェクトをビルド → IPA を生成 月15回(iOS)
EAS Submit 手元の IPA を App Store Connect にアップロード 制限なし

つまり、ビルドだけ別の手段で行えば、アップロードは引き続き EAS を使えるわけです。EAS Submit は Apple ID と API Key の管理をよしなにやってくれるので、自前で altool や Transporter を使うより圧倒的に楽です。

図解: 通常フロー vs 本記事のハイブリッドフロー

ビルド工程だけが差分で、アップロード工程(EAS Submit)は両方のパスで全く同じものを使うのがポイントです。図では EAS Submit を共有ノードにして、それを視覚化しています。

見ての通り、差分は Build 工程(左側)だけで、その先の EAS Submit → App Store Connect は完全に同一の経路です。この記事の核心洞察はここにあります: ビルドだけ差し替えれば、面倒なアップロード工程は EAS にそのまま任せられる。Apple ID 認証や API Key の扱いで消耗せずに済みます。


🤔 コラム: EAS Build を使わないのに、なぜ Expo アカウントが必要?

「GitHub Actions でビルドするなら Expo 無しで完結できるのでは?」と思うかもしれませんが、この記事の構成では Expo 無料アカウントは必須 です。理由は3つ。

1. 証明書管理を eas credentials に委譲している

Apple Developer Portal で Distribution Certificate や Provisioning Profile を自前で発行・管理するのは相当面倒です。CSR 生成、秘密鍵の管理、Provisioning Profile の更新、デバイス登録…… これを eas credentials が全て自動でやってくれます。

npx eas credentials
# → iOS → production → Set up everything
# → あとはメニュー選ぶだけで証明書・プロファイルが揃う

2. アップロードを eas submit に任せている

xcrun altool や Transporter を自前で扱うと、ASC API Key の認証エラーで消耗します(筆者は CryptoKitError -7 で半日溶かしました)。eas submit は同じ認証情報で一発で通ります。

3. GitHub Actions 内で eas-cli を使うセットアップに EXPO_TOKEN が要る

ワークフロー内の expo/expo-github-action@v8eas-cli をインストール&認証するために EXPO_TOKEN を参照します。

使う EAS 機能と課金の関係

EAS 機能 使うか? 制限 無料プランで使える?
eas credentials ✅ 使う なし ✅ 無料で OK
eas build 使わない(GitHub Actions で代替) 月15回
eas submit ✅ 使う なし ✅ 無料で OK

つまりこのハイブリッド構成は 「EAS の重量級ワーカー(Build)だけ迂回して、軽量な周辺機能(credentials + submit)は活用する」 という設計です。Expo Production プラン($19/月)への課金は一切不要です。

EXPO_TOKEN の取得方法

GitHub Actions 内で EAS CLI を認証させるには Expo のアクセストークン が必要です。取得手順は以下の通り。

  1. https://expo.dev にログイン
  2. 右上のアバター → Account settingsAccess tokens
  3. Create token」をクリック
  4. トークン名を入力(例: github-actions-ios-build)→ Create
  5. 表示されたトークン文字列をコピー(一度しか表示されないので注意
  6. GitHub リポジトリの Secrets に登録:
    gh secret set EXPO_TOKEN --body "<コピーしたトークン>"
    

このトークンは EAS CLI の認証だけに使われ、Apple や App Store Connect へのアクセスには関与しません。漏れても App Store に直接影響はないものの、Expo プロジェクトへのアクセス権を持つので厳重に管理してください。

EXPO_TOKEN の使われ方

ワークフロー YAML の以下の部分で使われます:

- name: Setup Expo
  uses: expo/expo-github-action@v8
  with:
    eas-version: latest
    token: ${{ secrets.EXPO_TOKEN }}  # ← ここで認証

この expo/expo-github-action@v8 アクションが:

  1. Node 環境に eas-cli をインストール
  2. EXPO_TOKEN で非対話的にログイン
  3. 以降のステップで npx expo prebuild 等が認証済み状態で動く

ようにしてくれます。対話的な eas login を CI で走らせずに済むのでこの方法が必須です。


解決策: GitHub Actions の macOS ランナーでビルド

なぜ GitHub Actions か

  • macOS ランナーが使える: macos-26 (Xcode 26.2) などが標準で提供されている
  • 無料枠が別腹: GitHub Actions は Private リポジトリで月2000分の無料枠。ただし macOS ランナーは10倍の係数で消費されるので実質200分
  • 1ビルド ≒ 25-30分 → 月6〜7回くらいは無料で回せる
  • Public リポジトリなら実質無制限
  • 使い慣れている: CI/CD として既に馴染みがある

もし GitHub Actions 無料枠も足りなくなったら、リポジトリを Public に変更すれば無制限になります。機密情報は GitHub Secrets で管理されているので安全です。

全体フロー

ローカル(WSL)、GitHub Actions の macOS ランナー、App Store Connect の3つのアクターがどう連携するかをシーケンス図で示します。

この図のポイントは、開発者はローカルからコマンドを叩くだけで、重い仕事はすべてクラウド(GitHub Actions + App Store Connect)でやってくれる点です。Mac を持っていなくても iOS リリースが完結します。


📘 コラム: そもそも .p12 って何?

次のセクションでいきなり base64 -w0 credentials/ios/dist-cert.p12 のようなコマンドが出てきますが、「そもそも .p12 って何?」という方のために軽く整理しておきます。iOS の署名まわりは独特の用語が多くて最初つまづきやすいポイントなので、ここで押さえておくと後が楽になります。

.p12 = PKCS#12 形式の証明書ファイル

中身は2つセットで入っています:

  1. 証明書本体(Apple が発行した公開鍵)
    • 「このアプリは本物の ◯◯ が署名したものだ」と証明する電子的ハンコ
  2. 秘密鍵(Private Key)
    • 実際にアプリに署名する時に使う鍵

iOS アプリに署名するには 証明書 + 秘密鍵の両方 が必須です。.cer(証明書のみ)形式のファイルでは署名できません。

拡張子の違い早見表

拡張子 中身 主な用途
.cer 証明書のみ(公開鍵) Apple Developer Portal からダウンロードされるファイル
.p12 証明書 + 秘密鍵 Keychain Access からエクスポート、CI/CD で使う
.mobileprovision Provisioning Profile 「どの証明書・どの Bundle ID・どのデバイスで使えるか」の紐付け定義
.p8 App Store Connect API Key App Store Connect への API アクセス用(アップロード認証など)

【補足】.p8 とは?(.p12 と別物です)

後ほど APP_STORE_CONNECT_API_KEY_BASE64 という Secret も登録しますが、ここで使うのは .p12 ではなく .p8 という別フォーマットのファイルです。名前が似ているので混乱しやすいポイント。

  • .p12 = アプリへの署名に使う(Distribution Certificate + 秘密鍵)
  • .p8 = App Store Connect API への認証に使う(Apple が発行する ECDSA 秘密鍵、PEM 形式)

.p8 は App Store Connect の「Users and Access」→「Integrations」→「App Store Connect API」でキーを発行するとダウンロードできます。ファイル名は AuthKey_XXXXXXXXXX.p8 のようになっていて、この XXXXXXXXXXKey ID です(APP_STORE_CONNECT_API_KEY_ID Secret として登録する値)。

.p8一度しかダウンロードできません。失くしたら再発行が必要なので、ダウンロード直後に安全な場所にバックアップしましょう。

役割の違いを整理するとこうなります:

ファイル 誰が・いつ使うか
.p12 xcodebuildIPA を作る時にアプリに署名する
.p8 eas submitaltoolIPA を App Store Connect にアップロードする時に API 認証する

つまり 「ビルド時の署名」と「アップロード時の認証」は別の鍵で行われている、というのが iOS リリースの仕組みです。

パスワードの役割

.p12 は書き出すときに必ずパスワードを設定します。これが GitHub Secrets の DISTRIBUTION_CERT_PASSWORD の正体です。

.p12 ファイル単体が漏れてもパスワードが分からなければ秘密鍵は取り出せません。とはいえ両方漏らすと完全にアウトなので、.p12 は絶対にコミットしないこと。

CI/CD での流れ(.p12 と .p8 の両方)

.p12(署名用)と .p8(API認証用)が、それぞれ別のタイミングで使われる様子を図示します。

この図で見えてくる重要な事実:

  • .p12 は署名工程(xcodebuild) で使われる
  • .p8 はアップロード工程(eas submit) で使われる
  • 2つの鍵は別のタイミング・別の目的で使われる → 混同しないよう注意

つまり次のセクションでやる作業は、「ローカルで持っている .p12 / .p8 を GitHub Actions のクラウド Mac に安全に渡して、それぞれ適切なタイミングで使わせる」 ということです。これが分かっていれば、後のコマンドの意味がスッと頭に入ります。


事前準備: GitHub Secrets の登録

まずビルドに必要な認証情報を GitHub Secrets に登録します。

Secrets の全体マップ

登録する Secret は合計で12個ほどあります。最初に全体像を把握すると混乱しません。役割ごとに4グループに分けると整理しやすいです。

この分類を頭に入れておくと、後で「あれ、この Secret どこで使うんだっけ?」と迷わなくなります。

必要な Secrets 一覧

Secret 名 内容 取得元
EXPO_TOKEN Expo アクセストークン(取得手順は先ほどのコラム参照) https://expo.dev → Account settings → Access tokens
APPLE_TEAM_ID Apple Developer Team ID(例: ABCDE12345 Apple Developer アカウント
DISTRIBUTION_CERT_BASE64 Distribution Certificate (.p12) の Base64 eas credentials
DISTRIBUTION_CERT_PASSWORD .p12 のパスワード eas credentials
PROVISIONING_PROFILE_BASE64 Provisioning Profile の Base64 eas credentials
PROVISIONING_PROFILE_NAME Provisioning Profile の名前 eas credentials
APP_STORE_CONNECT_API_KEY_ID ASC API Key ID App Store Connect
APP_STORE_CONNECT_ISSUER_ID ASC Issuer ID App Store Connect
APP_STORE_CONNECT_API_KEY_BASE64 AuthKey (.p8) の Base64 App Store Connect
EXPO_PUBLIC_* アプリの環境変数(Clerk 等) 自プロジェクト

証明書・プロファイルの取得コマンド

cd src/mobile  # Expoプロジェクトのルート
npx eas credentials
# → iOS → production → Credentials.json: Update credentials.json with values from EAS servers
# これで credentials/ios/dist-cert.p12 と profile.mobileprovision が生成される

Base64 エンコードして GitHub Secrets に登録

# ⚠️ 改行混入を防ぐため base64 -w0 を必ず使う
base64 -w0 credentials/ios/dist-cert.p12 | gh secret set DISTRIBUTION_CERT_BASE64
base64 -w0 credentials/ios/profile.mobileprovision | gh secret set PROVISIONING_PROFILE_BASE64

# AuthKey_XXXXXXXXXX.p8 も同様
base64 -w0 AuthKey_XXXXXXXXXX.p8 | gh secret set APP_STORE_CONNECT_API_KEY_BASE64

# その他の平文Secret
gh secret set APPLE_TEAM_ID --body "ABCDE12345"
gh secret set PROVISIONING_PROFILE_NAME --body "*[expo] com.example.yourapp AppStore"
gh secret set APP_STORE_CONNECT_API_KEY_ID --body "XXXXXXXXXX"
gh secret set APP_STORE_CONNECT_ISSUER_ID --body "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

重要: .p12.p8.gitignore に追加しておくことを忘れずに。誤コミットしないよう credentials/ フォルダごと無視するのが安全です。


GitHub Actions ワークフロー YAML

.github/workflows/ios-build.yml にこのファイルを配置します。コピペ可能な形にしてあります(プレースホルダは自分のプロジェクトに合わせて置換してください)。

name: iOS Production Build & Submit

on:
  workflow_dispatch:
    inputs:
      submit:
        description: 'Submit to App Store after build'
        required: false
        default: 'true'
        type: choice
        options:
          - 'true'
          - 'false'

jobs:
  build:
    runs-on: macos-26  # ⚠️ 2026/4/28 以降はXcode 26必須なので macos-26 を使う
    timeout-minutes: 60
    defaults:
      run:
        working-directory: src/mobile  # Expoプロジェクトのパス(プロジェクトルートなら削除)

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm install

      - name: Setup Expo
        uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}

      - name: Set environment variables
        run: |
          echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=${{ secrets.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY }}" >> .env
          echo "EXPO_PUBLIC_API_URL=${{ secrets.EXPO_PUBLIC_API_URL }}" >> .env
          # ↑ 自分のプロジェクトのEXPO_PUBLIC_*変数を全部ここに追加

      - name: Prebuild iOS
        run: npx expo prebuild --platform ios --clean

      - name: Decode signing certificate & provisioning profile
        env:
          DISTRIBUTION_CERT_BASE64: ${{ secrets.DISTRIBUTION_CERT_BASE64 }}
          DISTRIBUTION_CERT_PASSWORD: ${{ secrets.DISTRIBUTION_CERT_PASSWORD }}
          PROVISIONING_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}
          KEYCHAIN_PASSWORD: ci_keychain_password_2026
        run: |
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          CERT_PATH=$RUNNER_TEMP/distribution.p12
          echo -n "$DISTRIBUTION_CERT_BASE64" | base64 --decode -o $CERT_PATH
          security import $CERT_PATH -P "$DISTRIBUTION_CERT_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security list-keychain -d user -s $KEYCHAIN_PATH

          PP_PATH=$RUNNER_TEMP/profile.mobileprovision
          echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode -o $PP_PATH
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          PP_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< $(/usr/bin/security cms -D -i $PP_PATH))
          cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PP_UUID.mobileprovision

      - name: Install CocoaPods
        run: |
          cd ios
          pod install

      - name: Build iOS archive
        run: |
          cd ios
          xcodebuild -workspace YourApp.xcworkspace \
            -scheme YourApp \
            -configuration Release \
            -archivePath $RUNNER_TEMP/yourapp.xcarchive \
            -sdk iphoneos \
            archive \
            CODE_SIGN_STYLE=Manual \
            PROVISIONING_PROFILE_SPECIFIER="${{ secrets.PROVISIONING_PROFILE_NAME }}" \
            CODE_SIGN_IDENTITY="Apple Distribution" \
            DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}"

      - name: Export IPA
        env:
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          PP_NAME: ${{ secrets.PROVISIONING_PROFILE_NAME }}
        run: |
          cat > $RUNNER_TEMP/ExportOptions.plist << EOF
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
          <plist version="1.0">
          <dict>
            <key>method</key>
            <string>app-store-connect</string>
            <key>teamID</key>
            <string>${APPLE_TEAM_ID}</string>
            <key>uploadBitcode</key>
            <false/>
            <key>uploadSymbols</key>
            <true/>
            <key>signingStyle</key>
            <string>manual</string>
            <key>provisioningProfiles</key>
            <dict>
              <key>com.example.yourapp</key>
              <string>${PP_NAME}</string>
            </dict>
          </dict>
          </plist>
          EOF

          xcodebuild -exportArchive \
            -archivePath $RUNNER_TEMP/yourapp.xcarchive \
            -exportOptionsPlist $RUNNER_TEMP/ExportOptions.plist \
            -exportPath $RUNNER_TEMP/export

      - name: Upload IPA as artifact
        uses: actions/upload-artifact@v4
        with:
          name: yourapp-ios
          path: ${{ runner.temp }}/export/*.ipa

      - name: Cleanup keychain
        if: always()
        run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true

ビルド実行 → IPA ダウンロード → EAS Submit

ワークフローを手動トリガーします。

# 1. ビルド実行(submit=false を指定して IPA 生成のみ)
gh workflow run ios-build.yml --field submit=false

# 2. 実行中のワークフローを監視
gh run watch

# 3. 完了後、Run ID を取得
gh run list --workflow=ios-build.yml --limit 1

# 4. IPA アーティファクトをダウンロード
gh run download <RUN_ID> -n yourapp-ios -D /tmp/yourapp-ipa

# 5. EAS Submit で App Store Connect にアップロード(対話モードで Apple ID ログイン)
cd src/mobile
npx eas submit --platform ios --path /tmp/yourapp-ipa/YourApp.ipa

eas submit は Apple ID ログインを対話的に求めてきます。ここは EAS がよしなにやってくれるので任せてOKです。

アップロード完了後、5〜10分で App Store Connect の TestFlight タブにビルドが processing 完了した状態で現れます。あとは通常通り審査提出すれば完了です。


ハマったポイント(★経験談)

Gemini_Generated_Image_ui2ezgui2ezgui2e.png

この解決策に至るまでに踏み抜いた地雷を共有します。

1. altool によるアップロードは諦めた方がいい

当初、GitHub Actions 内で xcrun altool --upload-app を使って直接 App Store Connect にアップロードしようとしました。しかし、ASC API Key の認証で以下のエラーが連発。

Error: CryptoKitError.underlyingCoreCryptoError(error: -7)

.p8 ファイルの Base64 エンコードを何度もやり直したり、Key ID・Issuer ID を確認したりしましたが解決せず。最終的に eas submit に切り替えたら一発で通りました

教訓: ビルドは GitHub Actions、アップロードは EAS Submit、というハイブリッドが最適解。altool で消耗する意味はないです。

2. AuthKey .p8 の Base64 エンコードで改行混入

これが altool 失敗の一因でもありました。

# ❌ NG: 改行が混入してデコード失敗
base64 AuthKey_XXXXXXXXXX.p8 | gh secret set APP_STORE_CONNECT_API_KEY_BASE64

# ✅ OK: -w0 で改行を禁止
base64 -w0 AuthKey_XXXXXXXXXX.p8 | gh secret set APP_STORE_CONNECT_API_KEY_BASE64

Linux の base64 はデフォルトで76文字ごとに改行を入れます。これを GitHub Secrets に突っ込むと、復元時に改行コード混じりのバイナリになってデコードに失敗します。

3. Xcode workspace 名が app.json の name と一致しない

xcodebuild -workspace に渡す名前は、app.jsonname(表示名)とは一致しません。たとえば name にスペースや日本語が含まれていても、Expo はそれをそのままファイル名にはせず、内部ルールで英数字のみに正規化します。

正解は app.jsonios.bundleIdentifier でも slug でもなく、expo prebuild が生成した ios/ フォルダ内の実際のファイル名です。

npx expo prebuild --platform ios --clean
ls ios/*.xcworkspace  # ← ここで実際の名前を確認

たとえば name"My Cool App" でも、生成されるファイル名は MyCoolApp.xcworkspace のようにスペースが除去された形になります。Expo の正規化ルールは完璧に推測できないので、一度ローカルで prebuild して実際のファイル名を確認するのが確実です。確認した名前をワークフローの xcodebuild -workspace-scheme に指定してください。

4. Xcode バージョン要件(2026年4月28日以降)

これは全 Expo / iOS 開発者が影響を受ける要件なので絶対に押さえておいてください。

Apple は 2026年4月28日以降、iOS 26 SDK(Xcode 26)以降でビルドされたアプリしか新規申請・アップデートを受け付けない方針を発表しています。

  • macos-14 (Xcode 15) → NG
  • macos-15 (Xcode 16) → NG
  • macos-26 (Xcode 26.2) → ✅ OK

GitHub Actions でビルドしている場合、runs-on: macos-26 を指定する必要があります。EAS Build を使っている場合は Expo 側が自動で対応してくれます。

私はこの要件を知らずに初回ビルドを macos-15 で走らせてしまい、App Store Connect にアップロード後に警告が出ていました。処理はされますが、期限後は蹴られます。


注意事項

credentials を絶対にコミットしない

# .gitignore に追加
credentials/
credentials.json
*.p12
*.p8
*.mobileprovision
.env
.env.*

GitHub Secrets で管理するなら、ローカルにあるファイルはいつ誤コミットするか分かりません。.gitignore に入れるのが最初の一歩です。

GitHub Actions 無料枠の確認

# 現在の使用量を確認
gh api /user/settings/billing/actions

Private リポジトリの macOS ランナーは 10倍係数なので、実質月200分です。それを超えると Public にするか、有料プランに切り替える必要があります。


まとめ

項目
解決策 GitHub Actions macOS ランナー + EAS Submit のハイブリッド
コスト 無料(Expo Production $19/月 を回避)
初期セットアップ時間 約1〜2時間(Secrets 登録含む)
1ビルド所要時間 約25〜30分
月のビルド回数 GitHub Actions 無料枠内で約6〜7回
必要な前提 Apple Developer 登録済 + EAS で credentials 発行済

感想

正直、最初は「EAS Build 枠切れ」の時点で諦めて有料プラン加入を検討していました。しかし、EAS Build と EAS Submit が別サービスであるという事実に気づいた瞬間に全てが繋がりました。

  • ビルドだけ他の手段で行えばいい
  • アップロードの面倒は EAS に任せていい

Expo は「全部やってくれるプラットフォーム」ですが、部分的に切り出して使える柔軟性もあります。同じ問題に悩む方の参考になれば嬉しいです。


参考リンク


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?