Androidアプリの全自動リリース作業をする
この記事は2025 ZAICO アドベントカレンダーの12日目の記事です。
はじめに
Androidアプリのリリース作業、手順は決まっているのに毎回確認しながら進めていませんか?
「マイルストーンのIssueを確認して、リリースPRを作って、リリースノートを書いて、内部テストに配布して、確認が取れたら本番にプロモートして...」
こういった一連の流れを 「リリースして」の一言で実行できたら 便利ですよね。
弊社のアプリでもリリースは週に1回、多い時で2回する事もあるのですが、一部まだ手動で作業している所もあり、出来るだけリリース作業をする人にも負担をかけずにやりたい、出来る事ならほとんどの作業を自動化したいと思いから自動化を考えていました。
その考えていた点を解決するため、本記事ではClaude Code を使ってGitHub Actions と Fastlane を組み合わせ、自然言語でリリース作業を行う仕組みを構築してみたいと思います。
自動化の完成イメージ
作業者はClaudeに対してリリース作業を依頼するだけで後は対話していきながら自動でリリース作業を進めていける形となります。
作業者: 今週のリリースをお願い
Claude Code: 📋 マイルストーン「v2.2.0」を確認します...
完了したIssue: 8件
📝 リリースノートを生成中...
─────────────────────
【新機能】
・在庫データをCSV形式でエクスポートできるようになりました
【改善】
・バーコードスキャンの安定性を向上しました
─────────────────────
🚀 GitHub Actions をトリガーします...
✅ リリースPR作成完了: #234
✅ 内部テストへの配布開始
📣 Slackに通知しました
「🚀 v2.2.0 の内部テスト配信を開始しました」
前提条件
- GitHub Actions でのCI/CD環境が構築済み
- Fastlane で Google Play Console への配布設定済み
- Claude Code CLI がインストール済み
全体の流れ
対話をしながら実行していくワークフローは下記の通りとなります。
┌─────────────────────────────────────────────────────┐
│ Claude Code
│ 自然言語で指示 → 適切なワークフローを実行 │
└─────────────────────────────────────────────────────┘
│
├─── 1. マイルストーン確認 & リリースノート生成
│ └→ GitHub API でIssue/PR を取得し、AIが要約
│
├─── 2. GitHub Actions トリガー
│ └→ gh workflow run でリリースワークフローを実行
│
├─── 3. 内部テスト配布
│ └→ Fastlane supply で内部テストトラックに配布
│
└─── 4. 本番プロモート
└→ Fastlane supply で本番トラックに昇格
実装
Step 1: プロジェクトに CLAUDE.md を作成
Claude Code がプロジェクトのコンテキストを理解するために、CLAUDE.md をリポジトリルートに配置します。
このファイルに詳細に情報を書いていく事で、より精度の高いかつ曖昧な自然言語でも汎用性のある形でリリース作業を進める事ができます。
# プロジェクト概要
Android〇〇アプリのリポジトリです。
## リリースフロー
1. マイルストーンに紐づくIssue/PRがすべてクローズされていることを確認
2. GitHub Actions でリリースPRを作成
3. リリースPRにリリースノートを記載
4. マージ後、GitHub Actions で内部テストに配布
5. QA確認後、本番にプロモート
## バージョン管理
- `app/build.gradle.kts` で versionCode / versionName を管理
- マイルストーン名がバージョン名と対応(例: v2.2.0)
## GitHub Actions ワークフロー
### リリースPR作成 & 内部テスト配布
gh workflow run release-internal.yml -f version=<version>
### 本番プロモート
gh workflow run promote-production.yml -f version=<version>
## Slack通知
リリース通知は #dev-releases チャンネルに送信してください。
Webhook URL: $SLACK_WEBHOOK_URL 環境変数を使用
## 注意事項
- 本番プロモートは必ず確認を求めること
- リリースノートは日本語で作成
Step 2: GitHub Actions ワークフローの作成
リリースPR作成 & 内部テスト配布ワークフロー
このワークフローは workflow_dispatch で手動トリガーされ、以下を自動化します:
-
リリースブランチの作成:
release/v{version}ブランチを作成 -
バージョン更新:
build.gradle.ktsのversionCodeとversionNameを更新 - リリースノート生成: マイルストーンに紐づくIssue/PRから自動生成(ラベルで分類)
- リリースPR作成: 生成したリリースノートを本文としてPRを作成
- ビルド & 配布: AAB をビルドし、内部テストトラックに配布
Claude Code からは gh workflow run release-internal.yml -f version=2.2.0 で実行できます。
name: Release Internal Test
on:
workflow_dispatch:
inputs:
version:
description: 'リリースバージョン (例: 2.2.0)'
required: true
type: string
jobs:
create-release-pr:
runs-on: ubuntu-latest
outputs:
pr_number: ${{ steps.create-pr.outputs.pr_number }}
release_notes: ${{ steps.generate-notes.outputs.notes }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Create release branch
run: |
git checkout -b release/v${{ inputs.version }}
- name: Update version
run: |
# versionName を更新
sed -i "s/versionName = \".*\"/versionName = \"${{ inputs.version }}\"/" app/build.gradle.kts
# versionCode をインクリメント
CURRENT_CODE=$(grep -oP 'versionCode = \K\d+' app/build.gradle.kts)
NEW_CODE=$((CURRENT_CODE + 1))
sed -i "s/versionCode = $CURRENT_CODE/versionCode = $NEW_CODE/" app/build.gradle.kts
git add app/build.gradle.kts
git commit -m "Bump version to ${{ inputs.version }}"
- name: Generate release notes from milestone
id: generate-notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# マイルストーンからクローズされたIssue/PRを取得
MILESTONE="v${{ inputs.version }}"
echo "## 🎉 v${{ inputs.version }} リリースノート" > release_notes.md
echo "" >> release_notes.md
# 機能追加 (enhancement ラベル)
echo "### ✨ 新機能" >> release_notes.md
gh issue list --milestone "$MILESTONE" --state closed --label "enhancement" --json title,number \
--jq '.[] | "- \(.title) (#\(.number))"' >> release_notes.md
echo "" >> release_notes.md
# バグ修正 (bug ラベル)
echo "### 🐛 バグ修正" >> release_notes.md
gh issue list --milestone "$MILESTONE" --state closed --label "bug" --json title,number \
--jq '.[] | "- \(.title) (#\(.number))"' >> release_notes.md
echo "" >> release_notes.md
# その他
echo "### 🔧 その他の改善" >> release_notes.md
gh issue list --milestone "$MILESTONE" --state closed --json title,number,labels \
--jq '.[] | select(.labels | map(.name) | index("enhancement") == null and index("bug") == null) | "- \(.title) (#\(.number))"' >> release_notes.md
# 出力
NOTES=$(cat release_notes.md)
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Push release branch
run: |
git push origin release/v${{ inputs.version }}
- name: Create Pull Request
id: create-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_URL=$(gh pr create \
--title "🚀 Release v${{ inputs.version }}" \
--body-file release_notes.md \
--base main \
--head release/v${{ inputs.version }})
PR_NUMBER=$(echo $PR_URL | grep -oP '\d+$')
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "✅ Created PR: $PR_URL"
build-and-deploy-internal:
needs: create-release-pr
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: release/v${{ inputs.version }}
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Ruby for Fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Decode Keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/release.keystore
- name: Build Release APK/AAB
run: |
./gradlew bundleRelease
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- name: Deploy to Internal Test Track
run: |
bundle exec fastlane android internal
env:
SUPPLY_JSON_KEY_DATA: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
- name: Comment on PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr comment ${{ needs.create-release-pr.outputs.pr_number }} \
--body "✅ 内部テストトラックへの配布が完了しました!
📱 **テスト方法**
1. [Google Play Console](https://play.google.com/console/) で内部テストを確認
2. 内部テスターグループでインストール
🔍 **確認事項**
- [ ] 新機能の動作確認
- [ ] 既存機能のリグレッションテスト
- [ ] クラッシュが発生していないこと
確認完了後、このPRをマージして本番プロモートに進んでください。"
本番プロモートワークフロー
内部テストで検証済みのビルドを本番トラックに昇格させるワークフローです。
ポイントは段階的ロールアウトに対応していること。いきなり100%に配布するのではなく、まず10%のユーザーに配布し、問題がなければ徐々に拡大できます。Play Console から手動でロールアウト率を変更することも可能です。
Claude Code からは gh workflow run promote-production.yml -f version=2.2.0 -f rollout_percentage=10 で実行します。
name: Promote to Production
on:
workflow_dispatch:
inputs:
version:
description: 'プロモートするバージョン (例: 2.2.0)'
required: true
type: string
rollout_percentage:
description: 'ロールアウト率 (1-100)'
required: true
default: '10'
type: string
jobs:
promote:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Ruby for Fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Promote to Production
run: |
bundle exec fastlane android promote_to_production rollout:${{ inputs.rollout_percentage }}
env:
SUPPLY_JSON_KEY_DATA: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ inputs.version }}" \
--title "v${{ inputs.version }}" \
--generate-notes
Step 3: Fastlane の設定
Fastlane の supply アクションを使って Google Play Console への配布を自動化します。
事前準備として以下が必要です:
- Google Play Console で API アクセスを有効化
- サービスアカウントを作成し、JSON キーを取得
-
SUPPLY_JSON_KEY_DATA環境変数に JSON キーを設定
2つの lane を定義しています:
- internal: AAB を内部テストトラックにアップロード。内部テストは審査なしで最大100人のテスターに即座に配布できます
- promote_to_production: 内部テストから本番トラックへビルドを昇格。再アップロード不要でトラック間移動のみ行います
default_platform(:android)
platform :android do
desc "内部テストトラックにデプロイ"
lane :internal do
supply(
track: 'internal',
aab: 'app/build/outputs/bundle/release/app-release.aab',
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
desc "内部テストから本番にプロモート"
lane :promote_to_production do |options|
rollout = (options[:rollout] || 10).to_f / 100.0
supply(
track: 'internal',
track_promote_to: 'production',
rollout: rollout.to_s,
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
end
Step 4: Claude Code 用のカスタムコマンド作成
Claude Code では .claude/commands/ ディレクトリにマークダウンファイルを配置することで、カスタムコマンドを定義できます。/release と入力するだけで、定義した手順を Claude Code が実行してくれます。
ここでは2つのコマンドを作成します:
- /release: マイルストーン確認 → リリースPR作成 → 内部テスト配布 → Slack通知
- /promote: 最終確認 → 本番プロモート → Slack通知
カスタムコマンドには「何をどの順番で実行するか」を自然言語で記述します。Claude Code はこれを読み取って、適切なコマンドを実行してくれます。
リリース開始コマンド
# release
リリースプロセスを開始します。
## 実行手順
1. まず、対象のマイルストーンを確認してください
- `gh api repos/{owner}/{repo}/milestones --jq '.[] | select(.state=="open") | "\(.title): \(.open_issues)件未完了 / \(.closed_issues)件完了"'`
2. マイルストーンの未完了Issueがある場合は、ユーザーに確認を求めてください
3. 以下のコマンドでリリースワークフローをトリガーします
- `gh workflow run release-internal.yml -f version=<version>`
- version はマイルストーン名から `v` を除いた形式 (例: 2.2.0)
4. ワークフローの実行状況を確認します
- `gh run list --workflow=release-internal.yml --limit 1`
5. 完了したら、Slack に通知します(Webhook URLは環境変数 SLACK_WEBHOOK_URL を使用)
6. 最後に、作成されたPRのURLをユーザーに伝えてください
## 使用例
ユーザー: 今週のリリースをお願い
→ マイルストーン一覧を表示し、対象を確認後、リリースプロセスを実行
本番プロモートコマンド
# promote
内部テストアプリを本番環境にプロモートします。
## 実行前の確認事項
1. 対象のリリースPRを確認
- `gh pr list --state merged --limit 5`
2. 内部テストの結果を確認(ユーザーに口頭で確認)
- QAチームからの承認は得られていますか?
- Crashlyticsで新規クラッシュは報告されていませんか?
3. **必ずユーザーに最終確認を求めてください**
- 「本番環境へのプロモートを実行してよろしいですか?」
## 実行手順
1. 確認が取れたら、プロモートワークフローをトリガー
- `gh workflow run promote-production.yml -f version=<version> -f rollout_percentage=<percentage>`
- デフォルトのロールアウト率は 10%
2. ワークフローの実行状況を確認
- `gh run list --workflow=promote-production.yml --limit 1`
3. 完了したら、Slack に通知します
- 「🎉 v<version> を本番環境にリリースしました(ロールアウト: <percentage>%)」
## 使用例
ユーザー: 内部テストOKだったので本番に出して
→ 最終確認後、プロモートを実行
Slack通知用ヘルパースクリプト
#!/bin/bash
MESSAGE=$1
WEBHOOK_URL=${SLACK_WEBHOOK_URL}
if [ -z "$WEBHOOK_URL" ]; then
echo "Error: SLACK_WEBHOOK_URL is not set"
exit 1
fi
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"${MESSAGE}\"}" \
"$WEBHOOK_URL"
Step 5: 実際に使ってみる
内部テスト配布
$ claude
作業者: 今週のリリースをお願い
Claude Code: 📋 オープン中のマイルストーンを確認します...
v2.2.0: 0件未完了 / 8件完了 ✅
v2.3.0: 5件未完了 / 2件完了
v2.2.0 のすべてのIssueが完了しています。
このバージョンをリリースしますか?
作業者: はい、お願いします
Claude Code: 🚀 リリースワークフローを実行します...
$ gh workflow run release-internal.yml -f version=2.2.0
✅ ワークフローがトリガーされました
⏳ 実行状況を確認中...
Run #1234: in_progress
... (数分後) ...
✅ ワークフロー完了!
📝 作成されたPR: https://github.com/your-org/repo-android/pull/234
📱 内部テストトラックへの配布が完了しました
📣 Slackに通知しました:
「🚀 v2.2.0 の内部テスト配信を開始しました」
QAチームに確認を依頼してください。
確認が完了したら `/promote` で本番リリースできます。
本番プロモート
作業者: QA確認取れたので本番に出して
Claude Code: 📋 確認事項をチェックします...
最新のマージ済みリリースPR:
- #234: 🚀 Release v2.2.0 (2日前にマージ)
⚠️ 本番環境へのプロモートを実行します。
以下の内容で間違いないですか?
- バージョン: v2.2.0
- ロールアウト率: 10%(デフォルト)
実行してよろしいですか? (yes/no)
作業者: yes
Claude Code: 🚀 プロモートワークフローを実行します...
$ gh workflow run promote-production.yml -f version=2.2.0 -f rollout_percentage=10
✅ ワークフローがトリガーされました
⏳ 実行状況を確認中...
... (数分後) ...
✅ 本番プロモート完了!
📣 Slackに通知しました:
「🎉 v2.2.0 を本番環境にリリースしました(ロールアウト: 10%)」
Play Console でロールアウト状況を監視してください。
問題なければ、段階的にロールアウト率を上げていきましょう。
AIならではの付加価値
この仕組みの真価は、単なるコマンド実行の代行ではありません。
1. コンテキストを理解した判断
作業者: リリースして
Claude Code: ⚠️ マイルストーン v2.2.0 にはまだ未完了のIssueが2件あります:
- #230: 在庫インポート時のバリデーション強化 (P1)
- #232: ダークモード対応 (P2)
P1のIssueが含まれていますが、このままリリースしますか?
それとも次週に延期しますか?
2. リリースノートの自動生成・要約
マイルストーンのIssue/PRから、ユーザー向けの分かりやすいリリースノートを自動生成します。技術的な変更をエンジニア以外のメンバーにもわかりやすい文章に変換できます。
3. 異常検知と提案
作業者: 本番に出して
Claude Code: ⚠️ 内部テスト配布から24時間経過していません(現在: 18時間)
通常は24時間以上のテスト期間を設けています。
- このまま続行する
- 明日まで待つ(リマインドしますか?)
どちらにしますか?
最後に
Claude Codeと組み合わせてを使うことで、CI/CD パイプラインを実行しながら、自然言語でリリース作業を行えるようになりました。
また自然言語での対話ができる事で、マイルストーンの選択や途中で失敗やエラーになった際にも柔軟に対応が出来るのがAIの強みに思いました。
人間は「いいね、進めて」と言うだけ。これが AI 時代のリリースフローです。
参考リンク
次回はtakakさんのAPIドキュメントに関する興味深い話しです!お楽しみに!
Discussion