React Nativeアプリのリリース負荷を劇的に減らす!GitHub Actions入門

に公開

はじめに

こんにちは!React Native開発者の皆さん、こんな経験ありませんか?

「あー、またリリースか...」

  • iOSとAndroid、両方ビルドして...
  • 証明書の期限切れチェックして...
  • ストアにアップロードして...
  • メタデータ更新して...
  • あれ、バージョン番号間違えた...

1回のリリースに2時間かかって、しかも人的エラーで何度もやり直し...

Github ActionsとFastlane(次回内容)を使えば、リリース作業を大幅に短縮できます。

React Native開発におけるリリースの地獄

手動リリースの現実

React Nativeアプリのリリースって、結構大変じゃないですか?

# 典型的な手動リリースの流れ
$ # 1. コードの準備
$ git checkout main
$ git pull origin main
$ npm install

# 2. バージョン更新
$ # package.jsonを手動編集
$ # app.jsonのversionCodeを手動編集
$ # iOSのInfo.plistを手動編集

# 3. Androidビルド
$ cd android
$ ./gradlew clean
$ ./gradlew assembleRelease
$ # あれ、keystoreのパスワード何だっけ?
$ # 3回目で成功...

# 4. iOSビルド
$ cd ios
$ pod install
$ # 証明書の期限切れ??
$ # プロビジョニングプロファイル更新...
$ xcodebuild archive
$ # 1時間後...

# 5. ストアアップロード
$ # Google Play Consoleにログイン
$ # 手動でAABアップロード
$ # メタデータ入力...
$ # App Store Connectにログイン
$ # 手動でIPAアップロード
$ # スクリーンショット更新...

# 合計時間: 2時間

なんでこんなに大変なの?

React Native特有の問題:

  • 2つのプラットフォーム: iOSとAndroid、両方対応が必要
  • 複雑な署名: 証明書、プロビジョニングプロファイル、keystore
  • 複数のビルドツール: Xcode、Gradle、CocoaPods
  • 異なるストア: App Store、Google Play、それぞれ異なる手順
  • バージョン管理: package.json、build.gradle、Info.plistの同期

人的エラーが発生しやすい:

  • バージョン番号の不一致
  • 証明書の期限切れ
  • 環境変数の設定ミス
  • ストアのメタデータ入力ミス

GitHub Actionsでリリースを自動化

自動化後の理想的な流れ

# 開発者の作業
$ git push origin main

# あとは自動で...
# ✅ 両プラットフォーム同時ビルド
# ✅ 証明書の自動管理
# ✅ ストアへの自動アップロード
# ✅ リリースノートの自動生成
# ✅ チームへの通知

# 合計時間: 10分から15分程度

GitHub Actionsの基本概念

GitHub Actionsは、コードをプッシュしたら自動で何かしてくれる仕組みです。

React Native開発では:

  • コードプッシュ → 自動でビルド
  • ブランチマージ → 自動でリリース

ワークフロー設定の基本構造

React Native用の基本的なワークフロー

# .github/workflows/release.yml
name: React Native Release

# mainブランチにプッシュされた時に実行
on:
  push:
    branches:
      - main

jobs:
  release:
	# ビルドに使うOSを選択(今回は実機を使用 ※後述)
    runs-on: [self-hosted, macOS, ARM64]
    
    steps:
    - name: コードをチェックアウト
      uses: actions/checkout@v4
      
    - name: Node.jsをセットアップ
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'npm'
        
    - name: 依存関係をインストール
      run: npm ci
      
    - name: iOS依存関係をインストール
      run: |
        cd ios
        pod install
        
    - name: Androidビルド
      run: |
        cd android
        ./gradlew assembleRelease
    - name: iOSビルド
      run: |
        cd ios
        xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp
		
	#fastlaneでゴニョゴニョしてストアへアップロード

各セクションの説明

1. トリガー条件の設定

on:
  push:
    branches:
      - main

React Native開発でよく使うトリガー:

on:
  # mainブランチプッシュ時(リリース用)
  push:
    branches: ['main']
  
  # 特定ブランチプッシュ時(ベータ版用)
  push:
    branches: ['develop']
  
  # 手動実行(緊急時用)
  workflow_dispatch:

2. 実行環境の選択(泣く泣くセルフホストを選んだ理由)

jobs:
  release:
    runs-on: [self-hosted, macOS, ARM64]

macOSランナーだと無料枠が爆速で消える問題

最初は「GitHubのmacOSランナー使えば楽だよね〜」と思って設定したんですが、

# 最初の設定(失敗)
runs-on: macos-latest

# 結果:
# - 1回のビルドで25分
# - プライベートリポジトリの無料枠は月3000分
# - 月120回ビルドで無料枠を使い切る
# - 実際は月10-15回のリリースでも、テストやCIで制限に達する

macOSランナーの実行時間の倍率:

GitHub Actionsでは、ランナーの種類によって実行時間の倍率が異なります。

オペレーティングシステム 倍率
Linux 1倍
Windows 2倍
macOS 10倍

つまり、macOSランナーで25分ビルドすると、実際の使用量は250分として計算されます。これが無料枠を早く使い切る原因です。

macOSランナーの問題点:

項目 macOSランナー セルフホスト 改善効果
使用制限 3000分/月 無制限 制限なし
速度 25分/ビルド 10分/ビルド 2.5倍高速
安定性 時々謎失敗(正直これが一番辛い) 安定 エラー激減
制御 制限あり 完全制御 カスタマイズ自由

無料枠を気にしながら開発するのも嫌だし、万が一無料枠超えてしまったら稟議出さないとだし...
そこでセルフホストという選択をしました。

セルフホストを選んだ理由:

  1. 無料枠を使い切った: 月3000分では足りない
  2. ビルド遅すぎ: 25分 → 10分で完了
  3. 時々謎の失敗: セルフホストは安定

セルフホストランナーの設定:

セルフホステッドランナーの設定
※Github側の管理画面で設定が必要です。

セルフホスト導入の手順(簡単):

# 1. Mac mini M4を購入(約10万円)
# 2. GitHub Actionsランナーをインストール
$ curl -o actions-runner-osx-arm64-2.311.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-osx-arm64-2.311.0.tar.gz
$ tar xzf ./actions-runner-osx-arm64-2.311.0.tar.gz

# 3. 設定
$ ./config.sh --url https://github.com/your-org/your-repo --token YOUR_TOKEN

# 4. 起動
$ ./run.sh

# 結果:使用制限なしで高速ビルド!

環境変数とSecretsの使い方

React Native開発で必要なSecrets

# よく使うSecrets
env:
  # Android用
  ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
  ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
  ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
  ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
  
  # iOS用
  APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
  APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
  FASTLANE_TEAM_ID: ${{ secrets.FASTLANE_TEAM_ID }}
  
  # アプリ用
  API_URL: ${{ secrets.API_URL }}
  GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}

環境別の設定管理

# 環境別の設定例
- name: 環境設定を準備
  run: |
    if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
      echo "ENV=production" >> .env
      echo "API_URL=${{ secrets.PROD_API_URL }}" >> .env
    else
      echo "ENV=staging" >> .env
      echo "API_URL=${{ secrets.STAGING_API_URL }}" >> .env
    fi

ワークフローファイルを読んでみよう

シンプルなReact Nativeリリースワークフロー

name: React Native Release

on:
  push:
    branches: ['main']

jobs:
  release:
    runs-on: [self-hosted, macOS, ARM64]
    
    steps:
    - name: コードをチェックアウト
      uses: actions/checkout@v4
      
    - name: Node.jsをセットアップ
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'npm'
        
    - name: 依存関係をインストール
      run: npm ci
      
    - name: iOS依存関係をインストール
      run: |
        cd ios
        pod install
        
    - name: Androidビルド
      run: |
        cd android
        ./gradlew clean assembleRelease
        
    - name: iOSビルド
      run: |
        cd ios
        xcodebuild archive \
          -workspace MyApp.xcworkspace \
          -scheme MyApp \
          -archivePath MyApp.xcarchive
          
    - name: ビルド成果物をアップロード
      uses: actions/upload-artifact@v4
      with:
        name: release-builds
        path: |
          android/app/build/outputs/apk/release/
          ios/MyApp.xcarchive/

このワークフローの解説

  1. ブランチベースの実行: mainブランチへのプッシュでリリース
  2. 並行ビルド: AndroidとiOSを同時にビルド
  3. キャッシュ活用: npmの依存関係をキャッシュ
  4. 成果物保存: ビルド結果をアーティファクトとして保存
  5. 使用制限回避: セルフホストランナーで制限なし

よくある設定ミスとその対処法

1. 証明書の管理ミス

- name: iOSビルド
  run: xcodebuild archive  # 証明書が見つからないエラー

正解:

- name: 証明書をセットアップ
  run: |
    # 証明書をキーチェーンに追加
    security create-keychain -p "" build.keychain
    security import certificate.p12 -k build.keychain -P ${{ secrets.CERTIFICATE_PASSWORD }}
    security default-keychain -s build.keychain
    security unlock-keychain -p "" build.keychain
    
- name: iOSビルド
  run: xcodebuild archive

2. 環境変数の設定ミス

- name: アプリをビルド
  run: npm run build  # 環境変数が設定されていない

正解:

- name: アプリをビルド
  run: npm run build
  env:
    API_URL: ${{ secrets.API_URL }}
    GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}

3. バージョン管理のミス

# package.jsonとbuild.gradleのバージョンが不一致
# package.json: "1.0.0"
# build.gradle: versionCode 2

正解:

- name: バージョンを同期
  run: |
    # package.jsonからバージョンを取得
    VERSION=$(node -p "require('./package.json').version")
    
    # Androidのbuild.gradleを更新
    sed -i "s/versionName \".*\"/versionName \"$VERSION\"/" android/app/build.gradle
    
    # iOSのInfo.plistを更新
    /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" ios/MyApp/Info.plist

4. ビルド成果物の管理ミス

# ビルド成果物を保存していない
- name: ビルド
  run: npm run build
# 成果物が失われる...

正解:

- name: ビルド
  run: npm run build
  
- name: 成果物をアップロード
  uses: actions/upload-artifact@v4
  with:
    name: build-${{ github.run_number }}
    path: |
      android/app/build/outputs/
      ios/build/
    retention-days: 30

まとめ

今回はGitHub Actionsを使ったReact Nativeアプリのリリース自動化の基本について学びました:

  • 手動リリースの問題点と自動化のメリット
  • GitHub Actionsの基本概念と設定方法
  • macOSランナーの無料枠制限問題とセルフホストの必要性
  • React Native開発に必要な環境とSecrets
  • 実際のワークフローファイルの読み方
  • よくあるミスと対処法

リリース時間と使用制限の比較:

  • 手動: 2時間 + 人的エラーリスク
  • GitHub Actions (macOS): 25分 + 月3000分制限
  • GitHub Actions (セルフホスト): 10分 + 制限なし(泣く泣く導入したけど結果オーライ)

セルフホストランナーの導入効果:

  • 2.5倍高速化: 25分 → 10分
  • 使用制限なし: 月3000分制限から解放
  • 完全制御: 必要なツールを事前インストール
  • 導入は大変だったけど、結果的に大正解

次回は、さらに高度な自動化ツールFastlaneについて詳しく見ていきます!

Fastlaneを使えば、証明書管理、バージョンの設定やストアへのアップロードまでさらに高度に自動化できます!


次回内容: 「Fastlaneでモバイルアプリの配信を自動化」- ツール編

  • Fastlaneとは何か、なぜ使うのか
  • Android用Fastfileの詳細解説
  • iOS用Fastfileの詳細解説
  • 証明書管理の仕組み
  • ストア配信の自動化

お疲れ様でした!

NonEntropy Tech Blog

Discussion