React Native + Expoでモバイルアプリを開発していると、iOSとAndroidのビルドや配布、環境ごとの設定管理、証明書やキーストアの扱いに悩むことが少なくありません。本記事では、筆者がモバイルアプリの新規開発プロジェクトに2年間従事し、数十万ユーザー規模のアプリをReact Nativeで開発・運用する中で得た知見をもとに、Azure DevOps、Expo prebuild、Fastlaneを組み合わせたCI/CDパイプラインの実践的な構成と設計のポイントを紹介します。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
各社のエンジニアが発信する「テックブログ」。その熱量を@IT編集部がピックアップ。 生成AI前提時代に向けて環境が激変する中、“隣のエンジニア”は何を感じ、何を考え、どうアクションしているのか?――試行錯誤のプロセス、成功の鍵、変化に立ち向かうマインドなど現場のリアル、ナレッジを凝縮してお届けします。
本稿はTISインテックグループが運営する、開発現場から生まれた技術ノウハウを公開するサイト「Fintan」上で2025年12月12日に掲載した記事を転載するものです。そのため、用字用語の統一ルールなどが、@ITのものと異なります。ご了承ください。
※本稿で紹介する構成は Expo 49 / React Native 0.72 を前提とした構築事例であり、最新バージョンでは設定内容が異なる可能性があります
React Native + Expoでのモバイルアプリ開発において、環境ごとの設定管理、署名ファイルの扱い、TestFlightやGoogle Play Consoleへの配布を安定して運用するには、アプリ本体とは別にCI/CDプラットフォームの設計が重要になります。
本稿では、実際にプロジェクトで使用したReact Native+Expoアプリケーションのための、Azure DevOpsを用いた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によりiOSとAndroidの両プラットフォームに対して単一のコードベースで開発できるクロスプラットフォーム開発の効率性と、現在React Nativeの開発で主流となっているExpoの豊富な機能を活用できることが主な理由です。また、TISにはReact Nativeの豊富な開発実績と技術的知見があり、Fintanで公開されているノウハウを活用することで、効率的かつ高品質なアプリ開発を実現できると判断しました。
バックエンド基盤としてAzureを利用しており、リポジトリ管理からパイプライン実行までを同じエコシステムで統一できたためです。認証情報やパイプライン設定の管理を一元化しやすく、既存の開発基盤とも親和性が高い構成にできました。
CDパイプラインは実行時に環境名をパラメータとして受け取る設計になっています。なにも指定しない場合、デフォルト指定であるprodが実行されます(prod環境向けのCDパイプラインになります)。
実行時にパラメータで渡す環境名を変え、指定した環境用の設定ファイルを用意することで、同一のパイプライン定義ファイルを再利用して複数環境(prodに加えて、dev・stagingなど)用のパイプラインを作成することができます。
証明書やキーストア、APIキーなどの機密情報はAzure DevOpsのSecure FilesとPipeline Variablesで管理しています。
ビルド成果物(.ipa/.aab ファイル)をAzure Pipelinesのアーティファクトとして保存し、必要に応じて手動でダウンロード・配布できる仕組みを提供しています。
Azure DevOpsのYAMLに直接記述すると冗長になりがちな処理を、Fastlaneでシンプルなlaneにまとめるようにしています。特にTestFlightやGoogle Play Consoleへの複雑なAPI呼び出しを簡潔に記述することができます。
なお、本稿で紹介しているコードの一部は、mobile-app-crib-notes(Apache License 2.0)のソースコードを参考に作成しました。
CI/CDパイプラインを構築する前に、以下の準備作業が必要です。
ビルド時に必要な認証情報や証明書ファイルなど、機密性の高いファイルを安全に管理するため、Azure PipelinesのLibraryからSecure Filesに以下のファイルをアップロードします。ファイル名は環境変数で設定するため任意のファイル名で問題ありません。
| ファイル | |
| キーストアファイル 例)prod.keystore |
|
| Firebase構成ファイル 例)google-services-prod.json |
|
| Google Play Console API アクセス用のサービスアカウントキー 例)fastlane-supply-prod.json 注意:取得手順はドキュメントを参照 |
|
| ファイル | |
| 配布証明書 例)DistributionCertificate.p12 |
|
| Provisioning Profile 例)DistProfile-prod.mobileprovision |
|
| Firebase構成ファイル 例)GoogleService-Info-prod.plist |
|
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"
}
}
本プロジェクトでは、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/Fastfileにlaneを定義します。Expo prebuildによって生成されるandroidとiosフォルダはGitリポジトリにコミットせず、CI/CDパイプライン内で動的に作成する運用としています。
(Expoプロジェクトのルートディレクトリ) ├── fastlane │ └── Fastfile ├── src ├── assets ├── android ├── ios ├── app.config.js ├── package.json └── その他の設定ファイル
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
注意:1回の手順で作成できるパイプラインは1つのみです。複数のパイプラインを作成する場合は、パイプラインの数だけ手順を繰り返してください。
本プロジェクトでは、Azure DevOps PipelinesのYAMLをリポジトリで管理し、CIとCDをそれぞれ定義しました。ここではまず、developブランチへのプッシュやPR作成をトリガーに実行する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
パイプラインの処理フローは以下の通りです。
注意:プロジェクトでは以下のライブラリバージョンを利用しています。
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"
CDパイプラインでは、証明書やキーストア、APIキー、バンドルIDなどの値を環境変数として登録して利用します。これによって、機密情報をYAMLに直接記述せず管理できます。
| 変数名 | 説明 |
|---|---|
| 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構成ファイル名 |
| 変数名 | 説明 |
|---|---|
| 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」という名前でリポジトリにコミットされます。パイプラインに変更を加えたい場合は、リポジトリ内のこのファイルを編集することで、パイプラインの内容を更新できます。
以下の項目は、定期的なメンテナンスが必要です。
本記事では、Azure DevOpsを活用したReact Native+ExpoアプリケーションのCI/CDパイプライン構築事例をご紹介しました。
React Native+Expoアプリの開発・運用において、このパイプライン設定が皆様のプロジェクトの参考になれば幸いです。
Dagger、「Dagger Shell」をOSSとして公開 CI/CDパイプライン構築をどう効率化するのか?
CI/CDプラットフォームの老舗Buildkiteが「CI/CDパイプラインを開発者から隠すべきではない」と指摘する理由
CI/CDパイプラインで知っておくべき「9つの要素」 一般的な記事のおよそ5倍の大ボリュームで解説
「CI/CD」実践のポイント 取り組む前に把握しておきたいメリットと課題
「DevOpsチーム向けCI/CD」のベストプラクティス12選Copyright © ITmedia, Inc. All Rights Reserved.