OpenAIExpo2026/07/23 13:15

How Posh went from manual weekly mobile releases to continuous delivery with Expo

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

openaijamodel: gpt-5-mini-2025-08-07

PoshがExpoで手動の週次モバイルリリースから継続的デリバリへ移行

Key Points

  • OTAブランチで段階的配信
  • PRプレビューで即時検証
  • ネイティブ変更で自動バージョン管理

Summary

Poshは長時間かかる審査や週次の手動リリースに悩まされ、ExpoのFingerprintingとEAS Workflowsを活用してモバイルのCI/CDを全面的に再設計しました。PRプレビュー、OTAブランチ、バージョンブランチ、マスターオーケストレーションの4つの自動化で、週次手動リリースから週数回のOTAと月次ストア提出へと移行し、リリース速度と信頼性を向上させました。

Key Points

  • PRプレビュー

    • Expo FingerprintsでOTA可能かを判定して不要なビルドを防止。
    • PRにQRコード付きコメントを追加し開発アプリで即時確認可能に。
    • ネイティブ変更検出時にneeds new buildラベルを付与してフィルタリング。
  • OTAブランチ(例: ota_update_*)

    • masterへのマージは自動でOTAブランチを作成/リベースし、複数PRをまとめて段階的に配信。
    • OTAブランチ上のpushでプレビュー環境へOTA publishを実行。
    • チェンジログはOTAブランチに反映し追跡を容易に。
  • バージョンブランチ(ネイティブ変更発生時)

    • 既存OTAブランチを閉じ、バージョンブランチを作成しpackage.jsonのバージョンを自動更新。
    • build and submitラベルでのみストア向けビルド/提出を実行し、ステージング配信とE2Eテスター招待を自動化。
    • ビルド完了後にSlackでQAへ通知し、確認フローを短縮。
  • マスター(統合)ワークフロー

    • OTA/バージョンブランチの生成・リベース・クローズを管理。
    • OTA/バージョン統合時にEAS UpdateやGitHub Releaseを作成。
  • 実務的な推奨事項

    • 環境をdev/preview/productionに分離して1台で切り替え可能にする。
    • masterマージで自動的に本番デプロイしない(手動でのストア提出は保持)。
    • Expo FingerprintsとEASの事前パッケージ化コマンドを活用して安定したビルド/提出を目指す。

Result

  • 手動リリース工数を大幅削減し、リリース頻度と開発速度を改善。App Storeでカテゴリ1位を獲得する成長を支えた。

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

openaijamodel: gpt-5-mini-2025-08-07

Poshが週次の手動モバイルリリースからExpoを使った継続的デリバリへ移行した方法

ゲスト投稿 — David Davidov(Posh創業エンジニア)

これは、Posh(イベントを通じて主催者がコミュニティを作り、スケールし、マネタイズできるリアル体験のソーシャルマーケットプレイス)で創業エンジニアを務めるDavid Davidovによるゲスト投稿です。

数週間にわたる長いアプリ審査サイクルやタイムセンシティブな手動リリースに悩まされた結果、我々のチームはモバイルのCI/CDパイプラインを再構築し、可能な限り手動作業を排除する投資を行いました。参考までに、当社のアプリはiOSとAndroidで何百万ものユーザーにサービスを提供しているため、ストアへのリリースを安全かつ確実に、かつ予定通りに行うことは常に最優先事項でした。その投資は実を結び、リリース速度の改善はApp StoreのEntertainmentカテゴリでNo.1アプリを獲得する成長を支えるのに役立ちました。

Poshが大手と競う中で判明した課題

モバイルアプリの開発を始めてから2年以上、ストア経由のリリースという手動プロセスがずっと嫌いでした。我々はExpo Updatesを知っていましたが、fingerprintingやEAS Workflowsのpre-packaged jobs以前は、OTAアップデートの自動化はどこか不安定でエラーの余地が大きいと感じていました。

直近2年間で直面した主な痛点は次のとおりです。

  • Linear上のタスクが特定のアプリバージョンにいつリリースされたかの可視化がない。
  • 週替わりでRelease Trainマネージャーをまわす必要があり、その人がその週の変更セットのリリース責任を負っていた ← これが最大のボトルネック
  • リリースプロセスへのE2Eテストの自動統合
  • 維持が大変な不安定なGitHub Actionsでビルドや提出をキックしていた
  • エンジニアのPRが新しいネイティブ変更を導入したかどうかを理解する手段がない

これらすべての痛点が、モバイル上での開発をクリエイティブで高速な体験というよりは雑務のように感じさせていました。Expoのfingerprinting、workflows、OTAアップデートに関する進化を改めて見直した後、私はリリースシステムを根本から再設計し、重要な手動承認ポイントは残しつつ可能な限り自動化することにしました。この自動化により、我々は週次の手動リリースからほぼ毎月のストアリリースと週に複数回のOTAアップデートへと移行できました。

解決したかったこと

  • PRで導入された変更をインストール・テストする簡単な方法、かつそのPRがネイティブの変更を導入したかどうかを理解できること
  • EASビルドを過剰にトリガーしないシステム。テストを開始する準備ができたときだけ起動する
  • OTAアップデートを段階的に導入し、ステージングでテスト可能にすること(masterにマージされた変更が自動的に本番にデプロイされないようにする)
  • ネイティブ変更がmasterに入ったときだけ新しいバージョンブランチを作る(常にブランチを増やす必要を減らす)
  • 本番に新しいバージョンがリリースされたときに、チームに通知してそのバージョンに含まれる変更を自動で伝えることを排除する

以下では、本番リリースにかける時間を減らすために我々が行ったことを紹介します。

前提条件

  • Betoの「アプリ環境を分割する」ビデオを見ることを推奨します。これは特定のupdate channels / app environmentsを設定する際に必要になります。チュートリアルの最後には1台のデバイスにdevelopment、preview/staging、productionビルドをインストールできるようになります。これによりビルド間の切り替えが簡単になり、アプリのテストが容易になります。
  • プロジェクトに対してExpo UpdatesをExpo Fingerprintsで設定してください。

Automation 1: PRプレビューの設定

これはCI/CDワークフローに必須ではありませんが、特にagenticなワークフローを好むチームでは変更のレビューを速めるのに役立ちます。この記事を読んで刺激を受け、私はQRコードでPRにコメントしてdevelopmentアプリに新しい変更をインストールできるEAS workflowを作成しました。コードの詳細には深入りしませんが、このワークフローでいくつか変更した点は次の通りです。

  • fingerprintが見つからなければアプリをビルドしない(developmentにOTAできないため)。クレジット消費が膨らむと思ったのでこの判断にしました。将来的にエージェントを使ったパッケージ更新を自動化したくなれば変更するかもしれません。
  • 新しいネイティブコードを導入するPRには needs new build ラベルを付与する。これはGitHub PRのフィルタリング用です(ラベルは私のお気に入りです)。
  • Expoの例にあるコメントをカスタムに編集。いくつかの手順、当該fingerprintの最近のビルドへのリンク、トラブルシューティング手順、大きめのQRコードを追加しました(画面で目立つように)。

PR Review [object Object]

PR Review workflow [object Object]

これにより、DevinやClaudeに特定箇所の修正を依頼したときにすぐにアプリをフラッシュして何が変わったかを把握できます。外出先でもレビューして出荷できるようになりました🏃‍♂️✨

Automation 2: OTAブランチ

いくつかのPRをmasterにマージしたとします。それらのPRが自動的に本番へデプロイされるのは避けたいです。OTAブランチを作ることで、各PRがOTAをトリガーする代わりに複数の変更をまとめてOTAをバッチ化する機会を得られます。

OTAブランチでは2つのワークフローが必要です。

  • master/main用ワークフロー。これで以下を行います。

    • カレントのアプリバージョン下にOTAブランチを立ち上げる(任意でOTAブランチラベル、例: 8.0.0 OTA #1
    • ラベルによりPRのフィルタリングが容易になる✨
    • 既にOTAブランチが開いている場合はそれをmasterの上にリベースする
    • そのPR内のchangelogに新しく導入されたPRタイトルとIssue番号を自動で追加する
    • OTAブランチがmasterにマージされたときに本番向けのEAS Updateをトリガーするステップ
    • master/mainワークフローは継続して拡張します。最後のセクションでmasterワークフローの概観を示します。
  • OTAブランチに対するpushで動くワークフロー。これには次を含みます。

    • preview環境へのOTA publish

例は次のようになります(YAML):

name: OTA Preview
on:
  push:
    branches: ['ota_update_*']
jobs:
  fingerprint:
    name: Fingerprint
    type: fingerprint
    environment: preview
  publish_preview_android:
    name: Publish Android preview OTA
    needs: [fingerprint]
    type: update
    environment: preview
    params:
      platform: android
      branch: preview
  publish_preview_ios:
    name: Publish iOS preview OTA
    needs: [fingerprint]
    type: update
    environment: preview
    params:
      platform: ios
      branch: preview

OTA Update branch [object Object]

PR preview workflow [object Object]

OTAブランチ内の差分は基本的にchangelogの更新だけにするべきです。例:

## v8.15.1 OTA 1 (2026-04-13)
- Sample JS Changes white again #166
- Sample JS Changes green again #165
- Sample JS Changes #163
- Sample JS Changes #161
- Sample JS Changes #160
- Sample JS Changes #158

## v8.15.1 (older version)

最後に、changelogの使い方に関しては後の自動化セクションで再度触れます。

Automation 3: Versionブランチ

PRで新しいネイティブ変更が入ったとします。その場合、進行中のOTAブランチをクローズ(無効化)して、新しいversionブランチを立ち上げることを期待します。デプロイされるまでそのブランチを使います。これがエンジニアチームにとって、ストア向けの新しいリリースを作る必要があるかどうかの真実のソースになります。

VersionブランチでもOTAと同様に2つのワークフローを用意します。

  • masterワークフローを拡張してネイティブ変更を扱う。

    • もしOTAブランチが存在するならそれを閉じ、新しいVersionブランチを作成してVersion Branchラベルを付ける
    • mobile appのpackage.jsonのversionを自動で上げる
    • 既にversionブランチが存在する場合はそれをmasterにリベースし、OTAブランチと同様にchangelogを更新する
  • 手動でbuild & submit bundlesラベルを付けたときに実行されるワークフロー。

    • これにより、チームがビルドを切る準備ができたときだけストアに提出することを保証する
    • Expoのprepackaged workflowコマンドを使ってstagingトラック向けにビルド&提出する
    • 両ストアで新しいバンドルが検出されると、我々のE2Eテスターを自動招待する
    • 提出後、Slack経由でE2Eテスターにテスト開始を通知する

例(YAML):

name: Version Branch Builds
on:
  pull_request:
    types: [labeled]
concurrency:
  cancel_in_progress: true
  group: ${{ workflow.filename }}-${{ github.ref }}
jobs:
  build_production_android:
    name: Build Production Android
    if: ${{ github.event.label.name == 'build and submit' }}
    type: build
    environment: production
    params:
      platform: android
      profile: production
  build_production_ios:
    name: Build Production iOS
    if: ${{ github.event.label.name == 'build and submit' }}
    type: build
    environment: production
    params:
      platform: ios
      profile: production
  build_preview_android:
    name: Build Preview Android
    if: ${{ github.event.label.name == 'build and submit' }}
    type: build
    environment: preview
    params:
      platform: android
      profile: preview
  build_preview_ios:
    name: Build Preview iOS
    if: ${{ github.event.label.name == 'build and submit' }}
    type: build
    environment: preview
    params:
      platform: ios
      profile: preview
  build_development_android:
    name: Build Development Android
    if: ${{ github.event.label.name == 'build and submit' }}
    type: build
    environment: development
    params:
      platform: android
      profile: development
  build_development_ios:
    name: Build Development iOS
    if: ${{ github.event.label.name == 'build and submit' }}
    type: build
    environment: development
    params:
      platform: ios
      profile: development
  submit_production_android:
    name: Submit Production Android
    needs: [build_production_android]
    type: submit
    environment: production
    params:
      build_id: ${{ needs.build_production_android.outputs.build_id }}
      profile: production
  submit_production_ios:
    name: Submit Production iOS
    needs: [build_production_ios]
    type: submit
    environment: production
    params:
      build_id: ${{ needs.build_production_ios.outputs.build_id }}
      profile: production
  submit_preview_android:
    name: Submit Preview Android
    needs: [build_preview_android]
    type: submit
    environment: preview
    params:
      build_id: ${{ needs.build_preview_android.outputs.build_id }}
      profile: preview
  submit_preview_ios:
    name: Submit Preview iOS
    needs: [build_preview_ios]
    type: submit
    environment: preview
    params:
      build_id: ${{ needs.build_preview_ios.outputs.build_id }}
      profile: preview
  notify_testers:
    name: Notify QA Team
    needs: [build_preview_android, build_preview_ios, submit_preview_android, submit_preview_ios]
    environment: preview
    steps:
      - name: Send Slack message
        run: |
          # Notify your QA team that new preview builds are available.
          # Include whatever your team needs, such as build numbers,
          # install links, release notes, or testing instructions.
  comment_builds:
    name: Post Build Links
    needs: [ build_production_android, build_production_ios, build_preview_android, build_preview_ios, build_development_android, build_development_ios ]
    environment: production
    steps:
      - name: Post comment on PR
        run: |
          # Post a PR comment with links to the generated EAS builds.
          # The implementation depends on how your CI environment exposes
          # repository, pull request, and authentication context.

workflow graph [object Object]

PRには新しいバージョン番号を反映したchangelogとmobile appのpackage.jsonの変更が入るはずです。手動の部分はこれで提出して審査に出し、リリースボタンを押すことだけになります。バンドル以外の変更が含まれる可能性があるため、我々はその部分を自動化しない方針です。

Automation 4: masterワークフロー

この段階で、masterワークフローは次を担うべきです。

  • PRがマージされるたびにOTAブランチを作成・リベースする
  • PRがマージされるたびにversionブランチを作成・リベースし、重複するOTAブランチを閉じる
  • OTAまたはversionブランチがマージされたときにGitHub上で新しいリリースを作成する

例(YAML):

name: Production Deploy
on:
  push:
    branches: ['main']
concurrency:
  cancel_in_progress: true
  group: ${{ workflow.filename }}-${{ github.ref }}
jobs:
  fingerprint:
    name: Fingerprint
    type: fingerprint
    environment: production
  get_android_build:
    name: Check Android build
    needs: [fingerprint]
    type: get-build
    params:
      fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
      profile: produc

(注: 上記は抜粋です。実際のファイルでは更に多くのステップとエラーハンドリングがあります。)


これらの自動化を組み合わせることで、我々はリリースにかかる人的コストを大幅に削減し、頻繁なOTAと計画されたストア提出の両方を安全に実行できるようになりました。興味があれば、具体的なワークフローの全文やスクリプト例も共有します。