GitHub ActionsでAndroidリリースAPKを自動ビルドする
目次
- はじめに
- 完成イメージ
- 1. keystoreファイルの作成
- 2. GitHub Secretsへの登録
- 3. build.gradleへの署名設定
- 4. ワークフローの設定
- 5. 署名確認
- Tips: トリガー対象ファイルの絞り込み(paths)
- Tips: 手動実行(workflow_dispatch)について
- まとめ
はじめに
GitHub ActionsでAndroidアプリのリリースビルドを自動化する手順をまとめます。
PRマージ時の自動ビルドに加え、workflow_dispatch を使った任意タイミングでの手動実行にも対応します。
完成イメージ
- PRマージ時に自動でリリースAPKをビルド
- Actionsタブから手動でも任意ブランチに対してビルド実行
- ビルド完了後、PRにArtifactsのリンクをコメント通知
1. keystoreファイルの作成
JDK付属の keytool コマンドでkeystoreを生成します。
keytool -genkey -v \
-keystore release.keystore \
-alias my-key-alias \
-keyalg RSA \
-keysize 2048 \
-validity 10000
実行すると対話形式で以下を入力します(一部抜粋)。
キーストアのパスワードを入力してください:
新規パスワードを再入力してください:
識別名を入力します。サブコンポーネントを空のままにする場合はドット(.)を1つ入力し、
中カッコ内のデフォルト値を使用する場合は[ENTER]を押します。
姓名は何ですか
[Unknown]: Taro Yamada
組織単位名は何ですか
[Unknown]: MyTeam
...
生成した release.keystore は .gitignore に追加し、絶対にリポジトリにコミットしないこと。
2. GitHub Secretsへの登録
keystoreはバイナリファイルのため、Base64エンコードしてSecretsに登録します。
エンコード
# Mac/Linux
base64 -i release.keystore | pbcopy
# Windows (PowerShell)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")) | Set-Clipboard
GitHub Secretsが安全な理由・想定されるリスク
GitHub Secretsには以下のセキュリティ特性があります。
- クライアントサイドで暗号化 — 公式ドキュメントによると、SecretsはLibsodium sealed boxによってGitHubのサーバーに届く前に暗号化される。ワークフロー実行時まで復号されない
-
ログにマスクされる — ワークフローのログ上でSecretsの値は自動的に
***に置換される - 読み取り不可 — 登録後はGitHub UIからも値を参照できない(上書きのみ可能)
ただし、以下のリスクは理解しておく必要があります。
| リスク | 内容 |
|---|---|
| Write権限者は全員利用可能 | 公式ドキュメントに「リポジトリへのWrite権限を持つユーザーはSecretsにアクセスできる」と明記されている。同じプライベートリポジトリのメンバーは誰でもワークフロー経由でSecretsを利用でき、悪意あるワークフローを追加されると値を外部送信される可能性がある |
| ログへの意図しない出力 |
echo $SECRET のように明示的に出力するコードを書くとマスクが外れる場合がある。公式ドキュメントでは「ログの自動マスクを確実に機能させるために、Secretの値に構造化データを使わないこと」と明記されている |
| フォークからのPRは参照不可 | 外部コントリビューターのフォークPRではSecretsは渡されない(意図的な制限) |
GitHubの仕様上、リポジトリへのWrite権限を持つメンバーは全員、すべてのSecretsをワークフロー経由で利用できます(公式ドキュメント)。SecretsごとにRead/Writeを細かく制御する機能はないため、チーム開発ではリポジトリメンバーへのWrite権限付与自体を慎重に行うことが重要です。
Secretsの登録
リポジトリの Settings → Secrets and variables → Actions → New repository secret から以下4つを登録します。
| Secret名 | 値 |
|---|---|
KEYSTORE_BASE64 |
Base64エンコードしたkeystore |
KEYSTORE_PASSWORD |
keystoreファイル全体のパスワード |
KEY_ALIAS |
エイリアス名(例: my-key-alias) |
KEY_PASSWORD |
キー個別のパスワード |
KEYSTORE_PASSWORD と KEY_PASSWORD は別概念です。keystoreはファイル全体を保護するパスワード、KEY_PASSWORDはkeystore内の特定キーを保護するパスワードです。値を同じにすることも可能ですが、Secretsは別々に管理しておくことを推奨します。
3. build.gradleへの署名設定
CI(GitHub Actions)ではシークレットを環境変数から、ローカルでは local.properties から読み込む設定にします。これにより、keystoreの情報をコードにハードコードせず両環境に対応できます。
android {
val localProps = Properties().apply {
val f = rootProject.file("local.properties")
if (f.exists()) load(f.inputStream())
}
signingConfigs {
create("release") {
storeFile = file(
(System.getenv("KEYSTORE_PATH")
?: localProps.getProperty("KEYSTORE_PATH", "release.keystore")).trim()
)
storePassword = (System.getenv("KEYSTORE_PASSWORD")
?: localProps.getProperty("KEYSTORE_PASSWORD"))?.trim()
keyAlias = (System.getenv("KEY_ALIAS")
?: localProps.getProperty("KEY_ALIAS"))?.trim()
keyPassword = (System.getenv("KEY_PASSWORD")
?: localProps.getProperty("KEY_PASSWORD"))?.trim()
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
ローカルビルド用の設定
ローカルでリリースビルドする場合は local.properties に以下を追記します。
KEYSTORE_PATH=../release.keystore
KEYSTORE_PASSWORD=your_keystore_password
KEY_ALIAS=my-key-alias
KEY_PASSWORD=your_key_password
local.properties は .gitignore にデフォルトで含まれていますが、念のため含まれていることを確認してください。
優先順位は 環境変数 > local.properties となるため、CI環境では自動的にGitHub Secretsが使われます。
4. ワークフローの設定
.github/workflows/pull_request.yml を以下のように設定します。
name: Pull Request # ワークフローの名前
on: # ワークフローの発動条件
pull_request: # プルリクエスト
branches: # 対象ブランチ
- main
- 'feature/**'
paths:
- 'app/**' # appモジュール配下の変更時のみ
- 'build.gradle.kts' # ルートのGradle変更時も含める
types: # イベントタイプ
- closed # プルリクエストがクローズされたとき
workflow_dispatch: # UIでのワークフローのトリガー
permissions: # 権限
contents: read # リポジトリのコード取得に必要(upload-artifactはリポジトリへの書き込みを行わないためreadで十分)
pull-requests: write # PRにコメントする権限
jobs: # ジョブ
build: # ジョブの名前
# プルリクエストがマージされたとき及び、workflow_dispatchイベントが発生したとき
if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest # 実行環境
steps: # ステップ
- name: Checkout # チェックアウト
uses: actions/checkout@v4 # actions/checkout@v4 を使用
- name: Set Up JDK # JDK のセットアップ
uses: actions/setup-java@v4 # actions/setup-java@v4 を使用
with: # 設定
distribution: 'zulu' # Zulu ディストリビューション
java-version: '21' # Java 21
- name: Setup Gradle # Gradle のセットアップ
uses: gradle/actions/setup-gradle@v6 # gradle/actions/setup-gradle@v6 を使用
- name: Decode Keystore
# GitHub SecretsにBase64で保存したkeystoreをデコードしてファイルに書き出す
# ファイルはビルド後に自動削除されるランナーの一時領域に置く
# ${{ secrets.XXX }} をrunに直接展開せずenv経由で渡す
# 公式推奨: https://docs.github.com/en/actions/reference/security/secure-use#use-an-intermediate-environment-variable
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
run: |
echo "$KEYSTORE_BASE64" | base64 -d > ${{ github.workspace }}/release.keystore
- name: Build With Gradle # Gradle でビルド
run: ./gradlew assembleRelease # リリースビルド
env:
KEYSTORE_PATH: ${{ github.workspace }}/release.keystore
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- name: Upload APK # APK のアップロード
uses: actions/upload-artifact@v4 # v4 以降に変更
with: # 設定
name: app-release.apk # アーティファクト名
path: app/build/outputs/apk/release/app-release.apk # APK のパス
- name: Comment on PR
if: github.event.pull_request.merged == true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
gh pr comment "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}" --body "🚀 リリースビルドが完了しました。
APKファイルは以下のリンク先(Artifactsセクション)からダウンロードできます:
$RUN_URL"
5. 署名確認
ダウンロードしたAPKが正しく署名されているかは apksigner で確認できます。
~/Library/Android/sdk/build-tools/35.0.0/apksigner verify --verbose app-release.apk
apksigner はAndroid SDKのBuild Toolsに含まれています。上記はフルパス指定の例ですが、パスを通しておくとコマンド名だけで実行できます。
~/.zshrc(または ~/.bashrc)に以下を追記し、反映します。
export PATH="$PATH:$HOME/Library/Android/sdk/build-tools/35.0.0"
source ~/.zshrc
パスが通っているかは以下で確認できます。
which apksigner
# => /Users/yourname/Library/Android/sdk/build-tools/35.0.0/apksigner
署名されている場合、以下のように Verifies と表示されます。
Verifies
Verified using v1 scheme (JAR signing): false
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): false
...
Number of signers: 1
v2 scheme: true であれば署名は正常です。
Tips: トリガー対象ファイルの絞り込み(paths)
paths を追加することで、特定ファイルの変更時だけビルドをトリガーできます。READMEやドキュメントの修正など、アプリコードに関係ない変更でビルドが走るのを防げます。
on:
pull_request:
branches:
- main
- 'feature/**'
types:
- closed
paths:
- 'app/**' # appモジュール配下の変更時のみ
- 'build.gradle.kts' # ルートのGradle変更時も含める
| メリット | デメリット | |
|---|---|---|
paths あり |
ドキュメントやREADMEの修正でビルドが走らない。CI時間・コスト節約 | パス設定のミスでビルドがスキップされるリスクがある |
paths なし |
シンプルで確実にビルドが走る | 関係ない変更でもビルドが走る |
プロジェクトの規模が小さいうちは paths なしでシンプルに運用し、ドキュメント管理やモジュール分割が増えてきたタイミングで導入を検討するのがおすすめです。
Tips: 手動実行(workflow_dispatch)について
workflow_dispatch を設定することで、ActionsタブのUIから任意のタイミング・任意のブランチに対してビルドを実行できます。
注意点: workflow_dispatch のトリガーはワークフローファイルがデフォルトブランチに存在する場合のみ有効になります。featureブランチで追加しても「Run workflow」ボタンは表示されないため、mainへのマージ後に動作確認しましょう。
まとめ
| ステップ | 内容 |
|---|---|
| 1 |
keytool でkeystoreを生成 |
| 2 | Base64エンコードしてGitHub Secretsに4つ登録 |
| 3 |
build.gradle で環境変数・local.propertiesから署名設定を読み込む |
| 4 | ワークフローYAMLにDecodeとビルドステップを追加 |
| 5 |
apksigner でAPKの署名を確認 |
PRマージ時の自動化に加え workflow_dispatch を組み合わせることで、柔軟なCI/CDが構築できます。