検索
連載

「ビルドからテスト配信まで、iOSもAndroidもこれ一本」Azure DevOps×Fastlaneで実現するReact NativeアプリのCI/CDパイプライン:@IT Techブログ PickUp(TISインテックグループ編)

React Native + Expoでモバイルアプリを開発していると、iOSとAndroidのビルドや配布、環境ごとの設定管理、証明書やキーストアの扱いに悩むことが少なくありません。本記事では、筆者がモバイルアプリの新規開発プロジェクトに2年間従事し、数十万ユーザー規模のアプリをReact Nativeで開発・運用する中で得た知見をもとに、Azure DevOps、Expo prebuild、Fastlaneを組み合わせたCI/CDパイプラインの実践的な構成と設計のポイントを紹介します。

Share
Tweet
LINE
Hatena

注目のテックブログから「現場の知恵」をあなたに――

各社のエンジニアが発信する「テックブログ」。その熱量を@IT編集部がピックアップ。 生成AI前提時代に向けて環境が激変する中、“隣のエンジニア”は何を感じ、何を考え、どうアクションしているのか?――試行錯誤のプロセス、成功の鍵、変化に立ち向かうマインドなど現場のリアル、ナレッジを凝縮してお届けします。


本稿はTISインテックグループが運営する、開発現場から生まれた技術ノウハウを公開するサイト「Fintan」上で2025年12月12日に掲載した記事を転載するものです。そのため、用字用語の統一ルールなどが、@ITのものと異なります。ご了承ください。


「@IT Techブログ PickUp(TISインテックグループ編)」のインデックス

連載目次

※本稿で紹介する構成は Expo 49 / React Native 0.72 を前提とした構築事例であり、最新バージョンでは設定内容が異なる可能性があります

 React Native + Expoでのモバイルアプリ開発において、環境ごとの設定管理、署名ファイルの扱い、TestFlightやGoogle Play Consoleへの配布を安定して運用するには、アプリ本体とは別にCI/CDプラットフォームの設計が重要になります。

本稿では、実際にプロジェクトで使用したReact Native+Expoアプリケーションのための、Azure DevOpsを用いたCI/CDパイプラインの実装事例をご紹介します。

CI/CDプラットフォームの構成

 題材としたプロジェクトでは、React NativeとExpoを組み合わせたアーキテクチャを採用しています。CI/CDプラットフォームにはAzure DevOps Pipelinesを選定し、ソース管理にはAzure Reposを利用しています。ビルドおよびデプロイメントの自動化にはFastlaneを活用しています。

 開発したアプリケーションはiOSとAndroidの両プラットフォームに対応しており、iOSアプリはApp Store ConnectのTestFlight、AndroidアプリはGoogle Play Consoleを通じて配布します。

技術選定の背景

React Native + Expoを採用した理由

 React NativeによりiOSとAndroidの両プラットフォームに対して単一のコードベースで開発できるクロスプラットフォーム開発の効率性と、現在React Nativeの開発で主流となっているExpoの豊富な機能を活用できることが主な理由です。また、TISにはReact Nativeの豊富な開発実績と技術的知見があり、Fintanで公開されているノウハウを活用することで、効率的かつ高品質なアプリ開発を実現できると判断しました。

Azure DevOpsを採用した理由

 バックエンド基盤としてAzureを利用しており、リポジトリ管理からパイプライン実行までを同じエコシステムで統一できたためです。認証情報やパイプライン設定の管理を一元化しやすく、既存の開発基盤とも親和性が高い構成にできました。

本稿で紹介するパイプラインの特徴

環境ごとの切り替えに対応したパイプライン設計

 CDパイプラインは実行時に環境名をパラメータとして受け取る設計になっています。なにも指定しない場合、デフォルト指定であるprodが実行されます(prod環境向けのCDパイプラインになります)。

 実行時にパラメータで渡す環境名を変え、指定した環境用の設定ファイルを用意することで、同一のパイプライン定義ファイルを再利用して複数環境(prodに加えて、dev・stagingなど)用のパイプラインを作成することができます。

機密情報の安全な管理

 証明書やキーストア、APIキーなどの機密情報はAzure DevOpsのSecure FilesとPipeline Variablesで管理しています。

ビルド成果物の保存と再利用

 ビルド成果物(.ipa/.aab ファイル)をAzure Pipelinesのアーティファクトとして保存し、必要に応じて手動でダウンロード・配布できる仕組みを提供しています。

Fastlaneによる処理の集約

 Azure DevOpsのYAMLに直接記述すると冗長になりがちな処理を、Fastlaneでシンプルなlaneにまとめるようにしています。特にTestFlightやGoogle Play Consoleへの複雑なAPI呼び出しを簡潔に記述することができます。

 なお、本稿で紹介しているコードの一部は、mobile-app-crib-notes(Apache License 2.0)のソースコードを参考に作成しました。

CI/CD構築前に準備するもの

 CI/CDパイプラインを構築する前に、以下の準備作業が必要です。

Secure Filesの設定に機密ファイルをアップロードする

 ビルド時に必要な認証情報や証明書ファイルなど、機密性の高いファイルを安全に管理するため、Azure PipelinesのLibraryからSecure Filesに以下のファイルをアップロードします。ファイル名は環境変数で設定するため任意のファイル名で問題ありません。

Android用
ファイル
キーストアファイル
例)prod.keystore
Firebase構成ファイル
例)google-services-prod.json
Google Play Console API アクセス用のサービスアカウントキー
例)fastlane-supply-prod.json
注意:取得手順はドキュメントを参照


iOS用
ファイル
配布証明書
例)DistributionCertificate.p12
Provisioning Profile
例)DistProfile-prod.mobileprovision
Firebase構成ファイル
例)GoogleService-Info-prod.plist

package.jsonにCI/CD用スクリプトを定義する

 package.jsonに以下のスクリプトを定義します。これらのスクリプトはパイプライン実行時に利用します。

注意:各スクリプト内で利用されているコマンドのオプションは、本プロジェクトの構成や要件に応じて設定されています。プロジェクトごとに適宜調整してください。

{
	"scripts": {
		"build:plugin": "rimraf config/plugin/build && tsc --build config/plugin",
		"prebuild:prod": "run-s build:plugin && cross-env ENVIRONMENT=prod expo prebuild --clean --no-install",
		"lint:es": "eslint --ext .jsx,.js,.tsx,.ts .",
		"lint:tsc": "tsc --noEmit",
		"coverage": "jest --detectOpenHandles --runInBand --coverage --coverageReporters=cobertura"
	}
}

Expo Config Pluginでビルド設定を自動化する

 本プロジェクトでは、AndroidおよびiOSのリリースビルド設定を効率的かつ自動的に管理するために、以下のExpo Config Pluginを利用しています。

・1.Androidリリースビルド設定:withAndroidAppBuildGradleForRelease

 ExpoのCDパイプラインにおいて、キーストア情報などの機密データをgradle.propertiesファイルに書き込みます。このConfig Pluginは、Androidアプリのbuild.gradleファイルを拡張し、このgradle.properties内の値を参照して、署名付きのリリースビルドを可能にするための設定を自動で適用します。

・2.iOSビルド設定と署名:withIosSetCredentials

 このConfig Pluginは、Xcodeプロジェクトのビルド設定(具体的にはバンドルID、署名チームID、Provisioning Profileなど)を、Expoの設定ファイル(app.jsonやapp.config.js)で定義された値に基づいて自動で書き換えるために利用されます。

 これにより、iOSアプリのコード署名に必要な設定を手動でXcodeプロジェクトに適用する手間を省き、ビルドプロセスを自動化します。

 Config Pluginのプロジェクトへの取り込みおよび利用方法の詳細については、公式ドキュメント、Create and use config plugins - Expo Documentationを参考にしてください。

Fastlaneでビルドとデプロイの処理をまとめる

 ビルドおよびデプロイの処理はFastlaneに集約し、fastlane/Fastfileにlaneを定義します。Expo prebuildによって生成されるandroidとiosフォルダはGitリポジトリにコミットせず、CI/CDパイプライン内で動的に作成する運用としています。

(Expoプロジェクトのルートディレクトリ)
├── fastlane
│ └── Fastfile
├── src
├── assets
├── android
├── ios
├── app.config.js
├── package.json
└── その他の設定ファイル
Fastfileを配置するディレクトリ構成

Fastfile(一部抜粋)

 Fastfileには、iOSとAndroidそれぞれのprebuild、build、uploadに対応するlaneを定義します。以下では、iOSに関するlaneのみを掲載します。Androidも含めた全文を確認したい場合は、元記事をご参照ください。

platform :ios do
  desc "Prebuild Production Release"
  lane :prebuild_prod do
    # npmパッケージのインストールは、lane実行前に実施されている想定、かつExpoのprebuildだとbundle execを使用してCocoaPodsの依存パッケージをインストールしないため、
    # --no-installオプションを付けて依存パッケージはインストールしないようにする(このアプリでは、prebuildスクリプトの中で、--no-installオプションを付与している)
    # https://docs.fastlane.tools/actions/sh/
    sh("npm run prebuild:prod -- -p ios")
  end
  desc "Build Production Release"
  lane :build_prod do
    # CocoaPodsの依存パッケージをインストールする
    # https://docs.fastlane.tools/actions/cocoapods/
    cocoapods(
      repo_update: true,
      podfile: "ios/Podfile",
      # CI環境ではプロジェクト名などが変わってしまうため、Podfile.lockに差分が出てしまう
      deployment: false,
      silent: false,
    )
    bundle_id = ENV["BUNDLE_ID"]
    provisioning_profile_name = ENV["APPLE_DISTRIBUTION_PROVISIONING_PROFILE_SECURE_FILENAME"]
    workspace = ENV["WORKSPACE"]
    scheme = ENV["SCHEME"]
    # アプリのiOS向けビルド
    build_ios_app(
      # アプリ名が日本語の場合、prebuildした際に自動で生成されるscheme(設定ファイル)はapp、
      # workspace(フォルダ)はios/app.xcworkspaceになる
      workspace: workspace,
      scheme: scheme,
      configuration: "Release",
      output_directory: "ios/build",
      output_name: "app.ipa",
      export_options: {
        method: "app-store",
        provisioningProfiles: {
          bundle_id => provisioning_profile_name,
        },
      },
      silent: false,
      suppress_xcode_output: false,
    )
  end
  desc "Upload Production On TestFlight"
  lane :upload_prod_testflight do
    apple_id = ENV["APPLE_ID"]
    
    # ビルドしたアプリをTestFlightにアップロード
    upload_to_testflight(
      apple_id: "#{apple_id}",
      skip_submission: true,
      skip_waiting_for_build_processing: true,
      ipa: "ios/build/app.ipa",
    )
  end
end

Azure DevOpsのYAMLでCIパイプラインを定義する

注意:1回の手順で作成できるパイプラインは1つのみです。複数のパイプラインを作成する場合は、パイプラインの数だけ手順を繰り返してください。

 本プロジェクトでは、Azure DevOps PipelinesのYAMLをリポジトリで管理し、CIとCDをそれぞれ定義しました。ここではまず、developブランチへのプッシュやPR作成をトリガーに実行するCIパイプラインを紹介します。パイプライン作成の細かい手順を知りたい方は、元記事をご覧ください。

CIパイプライン(一部抜粋)

 このパイプラインは、developブランチへのプッシュやPR作成をトリガーに、ESLintとTypeScriptのリントを実行し、テストとカバレッジ計測を行います。最後に、Cobertura形式のカバレッジ結果とJUnit形式のテスト結果をAzure DevOpsに公開します。

trigger:
  - develop
pool:
  vmImage: ubuntu-latest
variables:
  - name: appDir
    value: '$(System.DefaultWorkingDirectory)'
  - name: npmConfigCache
    value: '$(Pipeline.Workspace)/.npm'
steps:
  # Node.jsをインストール
  - task: NodeTool@0
    inputs:
      versionSpec: ">=16.0.0"
  # npmのキャッシュを復元して依存関係をインストール
  - task: Cache@2
    inputs:
      key: 'npm | "$(Agent.OS)" | "$(appDir)/package-lock.json"'
      restoreKeys: |
        npm | "$(Agent.OS)"
      path: $(npmConfigCache)
  - task: CmdLine@2
    inputs:
      script: 'npm ci --cache $(npmConfigCache)'
      workingDirectory: '$(appDir)'
  # ESLintによる静的解析
  - script: |
      npm run lint:es
  # TypeScript型チェック
  - script: |
      npm run lint:tsc
  # テストとカバレッジ計測
  - script: |
      npm run coverage
  # カバレッジ結果を出力
  - task: PublishCodeCoverageResults@1 
    inputs:
      codeCoverageTool: Cobertura
      summaryFileLocation: $(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml
  # テスト結果を出力
  - task: PublishTestResults@2 
    inputs:
      testResultsFormat: "JUnit"
      testResultsFiles: $(System.DefaultWorkingDirectory)/coverage/jest-junit.xml

CDパイプライン

 パイプラインの処理フローは以下の通りです。

  1. 環境準備(Ruby、Node.js、Java/Xcodeのバージョン設定)
  2. 依存関係の復元(npmキャッシュ活用による高速化)
  3. ビルドに必要な機密ファイルの配置(証明書、設定ファイルのダウンロードとコピー)
  4. Expo Prebuild(ネイティブプロジェクトの動的生成)
  5. ビルド実行(Fastlaneによる自動化)
  6. 配布(TestFlight/Google Play Consoleへの自動アップロード)

注意:プロジェクトでは以下のライブラリバージョンを利用しています。
Expo:49.0.16
React Native:0.72.6

 以下では、iOS向けCDパイプラインを抜粋して掲載します。iOS向けCDパイプラインのうち、環境準備、証明書のインストール、Expo prebuild、ビルド、TestFlightへのアップロード、成果物保存の流れが分かる部分を抜粋したものです。npmキャッシュの復元や設定ファイルの配置など、一部の補助的なステップは省略しています。

 Android 向けのファイルを含め、パイプラインの全文を確認したい場合は、元記事を参照してください。

trigger: none
parameters:
  - name: environment
    default: prod
variables:
  - name: appDir
    value: "$(System.DefaultWorkingDirectory)"
  - name: npmConfigCache
    value: "$(Pipeline.Workspace)/.npm"
jobs:
  - job: build_${{parameters.environment}}
    pool:
      vmImage: macOS-14
    steps:
      # Ruby 3.3をインストール
      - task: UseRubyVersion@0
        inputs:
          versionSpec: "3.3"
          addToPath: true
      # Apple Distribution証明書をインストール(App Store配布用)
      - task: InstallAppleCertificate@2
        inputs:
          certSecureFile: $(APPLE_DISTRIBUTION_CERTIFICATE_SECURE_FILENAME)
          certPwd: $(APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD)
      # App Store配布用Provisioning Profileをインストール
      - task: InstallAppleProvisioningProfile@1
        inputs:
          provProfileSecureFile: $(APPLE_DISTRIBUTION_PROVISIONING_PROFILE_SECURE_FILENAME)
      # Xcode 16.2を使用するように設定
      - task: CmdLine@2
        inputs:
          script: "sudo xcode-select --switch /Applications/Xcode_16.2.app"
      # Node.js 18.19.0をインストール
      - task: NodeTool@0
        inputs:
          versionSpec: "18.19.0"
…
      # Gemfile.lockの依存関係をインストール
…
      # 環境別のFirebase設定ファイルをダウンロード
…
      # Firebase設定ファイルをプロジェクトルートにコピー
…
      # Firebase設定ファイルを標準名にリネーム
…
      # Expo prebuildでネイティブiOSプロジェクトを生成
      - task: CmdLine@2
        inputs:
          script: "fastlane ios prebuild_${{parameters.environment}}"
          workingDirectory: "$(appDir)"
      # XcodeでiOSアプリをビルド
      - task: CmdLine@2
        inputs:
          script: "fastlane ios build_${{parameters.environment}}"
          workingDirectory: "$(appDir)"
      # TestFlightにアップロード(内部テスト用)
      - task: CmdLine@2
        inputs:
          script: |
            export APPLE_ID=$(APPLE_ID)
            export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=$(FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD)
            fastlane ios upload_${{parameters.environment}}_testflight
          workingDirectory: "$(appDir)"
      # ビルド成果物をパイプライン成果物として保存
      - task: PublishPipelineArtifact@1
        inputs:
          targetPath: "$(appDir)/ios/build"
          artifactType: "pipeline"
          artifactName: "app.ipa"
iOS

CDパイプラインで利用する環境変数を登録する

 CDパイプラインでは、証明書やキーストア、APIキー、バンドルIDなどの値を環境変数として登録して利用します。これによって、機密情報をYAMLに直接記述せず管理できます。

AndroidのCDパイプラインに必要な環境変数
変数名 説明
ANDROID_RELEASE_INHOUSE_KEYSTORE_SECURE_FILENAME キーストアのファイル名
ANDROID_RELEASE_INHOUSE_KEYSTORE_PASSWORD キーストアのパスワード
ANDROID_RELEASE_INHOUSE_KEY_ALIAS キーのエイリアス名
ANDROID_RELEASE_INHOUSE_KEY_PASSWORD キーのパスワード
GOOGLE_SERVICES_JSON_SECURE_FILENAME Firebase構成ファイル名


iOSのCDパイプラインに必要な環境変数
変数名 説明
APPLE_DISTRIBUTION_CERTIFICATE_SECURE_FILENAME 配布証明書のファイル名
APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD 証明書のパスワード
APPLE_DISTRIBUTION_PROVISIONING_PROFILE_SECURE_FILENAME Provisioning Profileのファイル名
GOOGLE_SERVICES_INFO_PLIST_SECURE_FILENAME Firebase構成ファイル名
APPLE_ID App Store Connectの [App情報] セクションのApple IDプロパティ
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD アプリケーション固有のパスワード
注意:取得手順はドキュメントを参照
FASTLANE_USER App Store Connect/Apple Developer Portal ユーザーのメールアドレス
BUNDLE_ID バンドルID
FASTLANE_SUPPLY_JSON_FILE_NAME Google Play Console APIアクセス用のサービスアカウントキーのファイル名
WORKSPACE ワークスペース ファイルへのパス
SCHEME プロジェクトのスキーム

パイプラインの保存

 パイプラインを保存します。作成したパイプラインは「azure-pipelines-1.yml」という名前でリポジトリにコミットされます。パイプラインに変更を加えたい場合は、リポジトリ内のこのファイルを編集することで、パイプラインの内容を更新できます。

定期的なメンテナンスが必要な項目

 以下の項目は、定期的なメンテナンスが必要です。

  • 配布証明書:年 1 回の更新が必要
  • Provisioning Profile: 年 1 回の更新が必要
  • 実行環境:macOS、Xcode、Node.js、Java、Rubyのバージョンを定期アップデート

まとめ

 本記事では、Azure DevOpsを活用したReact Native+ExpoアプリケーションのCI/CDパイプライン構築事例をご紹介しました。

 React Native+Expoアプリの開発・運用において、このパイプライン設定が皆様のプロジェクトの参考になれば幸いです。

参考資料

元ブログもチェック!

元記事はコチラ。ぜひチェックしてみてください!


Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る