A template for building and shipping signed Android APKs entirely in GitHub Actions — no Android Studio installation needed.
I made this because I wanted to build Android side projects on a shared PC without installing Android Studio. If that sounds familiar, this might be useful.
| Situation | How it helped me |
|---|---|
| Shared or low-spec PC where Android Studio is hard to run | Everything ran on GitHub Actions — I didn't need to install anything locally |
| Starting a quick side project | Minimal boilerplate; I just pushed and a build started |
| Not sure how Android keystores work | Running one workflow generated a keystore and registered the signing secrets to Settings → Secrets and variables → Actions |
| Sharing APKs with friends or testers | Signed APKs showed up in the Actions Artifacts tab, ready to download |
| Cautious about supply chain risks | All third-party Actions are pinned to immutable commit SHAs |
① Use this template → ② Add GH_PAT secret → ③ Run generate-keystore
→ ④ Push your code → ⑤ Download signed APK from Artifacts
| Workflow | Trigger | Output | Kept for |
|---|---|---|---|
android-ci.yml |
Push / PR on main, master, develop (paths filtered — Android/build files only) |
Debug APK | 30 days |
release.yml |
Manual or push a v* tag |
Signed release APK + GitHub Release | 90 days |
generate-keystore.yml |
Manual — run once | Keystore auto-saved as repo Secrets | — |
Do this before running any workflow.
Open app/build.gradle.kts and update both lines:
- namespace = "com.example.myapp"
- applicationId = "com.example.myapp"
+ namespace = "com.yourname.yourapp"
+ applicationId = "com.yourname.yourapp"Then rename the source directory to match, and update the package declaration inside each Kotlin file:
app/src/main/java/com/example/myapp/ → app/src/main/java/com/yourname/yourapp/
app/src/test/java/com/example/myapp/ → app/src/test/java/com/yourname/yourapp/
Also update the first line of every .kt file:
- package com.example.myapp
+ package com.yourname.yourappGitHub's built-in GITHUB_TOKEN can't write repository Secrets — you need a Personal Access Token once.
- GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Click Generate new token · Scope: Only select repositories → pick this repo
- Permissions → Repository permissions → Secrets → Read and write · Generate & copy the token
- Repo → Settings → Secrets and variables → Actions → New repository secret
Name:
GH_PAT· Value: paste token → Add secret
This is a one-time setup. Everything after this runs automatically.
Actions → "Generate Android Keystores" → Run workflow
One thing that helped me: the workflow generates a keystore and registers the signing secrets to Settings → Secrets and variables → Actions — I didn't need to touch keytool.
All fields have sensible defaults — just click Run if you're unsure:
| Input | Default | Notes |
|---|---|---|
key_alias |
release |
Name for the signing key |
key_cn |
repo name | Certificate Common Name |
key_o |
owner name | Organization field |
key_c |
US |
ISO country code |
validity_days |
10000 (~27 yrs) |
How long the cert is valid |
After the workflow finishes, these Secrets show up in Settings → Secrets and variables → Actions:
| Secret | What it contains |
|---|---|
ANDROID_KEYSTORE_BASE64 |
Release keystore (Base64-encoded) |
ANDROID_KEYSTORE_PASSWORD |
Keystore password |
ANDROID_KEY_ALIAS |
Key alias |
ANDROID_KEY_PASSWORD |
Key password |
ANDROID_DEBUG_KEYSTORE_BASE64 |
Debug keystore (same cert on every build) |
Verify at Settings → Secrets and variables → Actions.
Note
Re-running this workflow replaces all five Secrets with a freshly generated keystore.
Push Android/build file changes to main and the debug build runs on its own.
Note:
android-ci.ymlusespathsfilters. Pushes that touch only unrelated files (e.g., README edits) will not trigger the workflow — that is expected behavior.
- Trigger: push or PR to
main,master, ordevelopwhen the changed files match the path filters - Download: Actions → job → Artifacts →
app-debug→app-debug.apk - Retention: 30 days
- If
ANDROID_DEBUG_KEYSTORE_BASE64is set, the same signing certificate is used every time
Option A — click to release:
- Push and create the tag first:
git tag v1.0.0 && git push origin v1.0.0 - Actions → "Release Build" → Run workflow
- Enter the tag you just pushed (e.g.
v1.0.0) → Run
Important: Manual dispatch checks out the tag you enter. The tag must already exist in the repo before you run the workflow, or the checkout step will fail.
Option B — tag to release (fully automatic):
git tag v1.0.0
git push origin v1.0.0
# → workflow triggers, APK built and attached to a GitHub Release automatically- Download: Actions → job → Artifacts →
app-release-signed→app-release.apk - Retention: 90 days
- The APK is also attached to a GitHub Release on the Releases page
Prerequisite: Secrets from Step 2 must be registered before running this.
Requires Java 17+ and the Android SDK (command-line tools, platform, and build-tools) — GitHub-hosted runners have these pre-installed, but you need to install the SDK locally if it isn't already set up (Android SDK setup guide):
# Debug APK → app/build/outputs/apk/debug/app-debug.apk
./gradlew assembleDebug
# Release APK (needs signing env vars set — normally handled by CI)
./gradlew assembleRelease.
├── app/
│ ├── build.gradle.kts # ← App ID, SDK versions, signing config
│ ├── proguard-rules.pro
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── java/com/yourname/yourapp/
│ │ └── MainActivity.kt
│ └── res/
├── build.gradle.kts # Root build config
├── settings.gradle.kts
├── gradle.properties
├── gradlew / gradlew.bat
└── .github/workflows/
├── android-ci.yml # Auto debug build on push
├── release.yml # Signed release build
└── generate-keystore.yml # One-time keystore + secret setup
🔒 Security: why Actions are pinned to commit SHAs
Workflows in this template reference Actions like this:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6Why not @v6?
Version tags are mutable — the action author can silently point v6 at entirely different code. If that happens (accident, compromised account, or supply-chain attack), every repo using @v6 would execute the attacker's code on the next run — with full access to your repository Secrets.
Why a SHA is safer
A commit SHA is immutable. Pinning to a SHA guarantees you always run exactly the code you reviewed. The human-readable tag stays as a comment so you know which version you're on.
🇯🇵 日本語版 README(クリックで展開)
GitHub Actionsだけでビルドと署名ができるテンプレートを作ってみました。共有PCしか手元になかったので、同じような状況の方の参考になれば。
| こんな状況に | この部分が助かった |
|---|---|
| 非力なPCやAndroid Studioが動かせない環境 | GitHub Actionsで動くので、ローカルには何もインストールしなくてすんだ |
| サイドプロジェクトをサクッと始めたい | 最小構成でpushすればビルドが走る |
| keystoreや署名まわりがよくわからない | ワークフローを動かすとkeystoreが生成されて Settings → Secrets and variables → Actions に登録された(keytoolコマンドを触らずに済んだ) |
| 友人・テスターにAPKを渡したい | ActionsのArtifactsタブから署名済みAPKをダウンロードできる |
| サプライチェーンリスクが気になる | サードパーティのActionをすべてコミットSHAでピン留めしている |
| ワークフロー | トリガー | 出力 | 保存期間 |
|---|---|---|---|
android-ci.yml |
main / master / develop への Push / PR(対象 paths の変更時のみ) |
デバッグ APK | 30日 |
release.yml |
手動 または v* タグ push |
署名済みリリース APK + GitHub Release | 90日 |
generate-keystore.yml |
手動(初回のみ) | キーストア+Secretsを自動登録 | — |
app/build.gradle.kts を開いて両方の行を変更:
- namespace = "com.example.myapp"
- applicationId = "com.example.myapp"
+ namespace = "com.yourname.yourapp"
+ applicationId = "com.yourname.yourapp"ソースディレクトリ名も合わせて変更し、各Kotlinファイルの package 行も更新してください:
app/src/main/java/com/example/myapp/ → app/src/main/java/com/yourname/yourapp/
app/src/test/java/com/example/myapp/ → app/src/test/java/com/yourname/yourapp/
各 .kt ファイルの先頭行も変更:
- package com.example.myapp
+ package com.yourname.yourappGITHUB_TOKEN はリポジトリのSecretsに書き込めないため、Personal Access Tokenが1回だけ必要です。
- GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Generate new token をクリック · Only select repositories → このリポジトリを選択
- Permissions → Repository permissions → Secrets → Read and write · 生成してコピー
- リポジトリ → Settings → Secrets and variables → Actions → New repository secret
名前:
GH_PAT· 値: コピーしたトークン → Add secret
初回のみの作業です。以降はすべて自動で動きます。
Actions → "Generate Android Keystores" → Run workflow
少し助かったことの一つ:このワークフローを動かすと、keystoreが生成されて署名用のSecretsが Settings → Secrets and variables → Actions に登録されました。keytoolは触らずに済みました。
すべての入力項目にデフォルト値があるので、迷ったらそのまま Run でOKです:
| 入力項目 | デフォルト | 説明 |
|---|---|---|
key_alias |
release |
署名キーの名前 |
key_cn |
リポジトリ名 | 証明書のCommon Name |
key_o |
オーナー名 | 組織名 |
key_c |
US |
ISO国コード |
validity_days |
10000(約27年) |
証明書の有効期間 |
ワークフロー完了後、以下のSecretsが Settings → Secrets and variables → Actions に登録されます:
| Secret名 | 内容 |
|---|---|
ANDROID_KEYSTORE_BASE64 |
リリース用キーストア(Base64) |
ANDROID_KEYSTORE_PASSWORD |
キーストアパスワード |
ANDROID_KEY_ALIAS |
キーエイリアス |
ANDROID_KEY_PASSWORD |
キーパスワード |
ANDROID_DEBUG_KEYSTORE_BASE64 |
デバッグ用キーストア(毎回同じ証明書) |
Settings → Secrets and variables → Actions で確認できます。
[!NOTE] このワークフローを再実行すると、5つのSecretsがすべて新しいキーストアで上書きされます。
Android/ビルド関連ファイルを main ブランチに push するとデバッグビルドが自動で実行されます。
注意:
android-ci.ymlにはpathsフィルタがあります。READMEだけの変更など、対象外のファイルのみを変更したpushではワークフローが起動しません(これは正常な動作です)。
- トリガー:
main、master、developへのpush または PR(pathsフィルタに合致する変更のみ) - ダウンロード: Actions → ジョブ → Artifacts →
app-debug→app-debug.apk - 保存期間: 30日間
ANDROID_DEBUG_KEYSTORE_BASE64を登録していれば、毎回同じ証明書でビルドされます
方法A — 手動実行:
- 先にタグを作成してpush:
git tag v1.0.0 && git push origin v1.0.0 - Actions → "Release Build" → Run workflow
- 作成したタグ(例:
v1.0.0)を入力して Run
重要: 手動実行時はタグ名で
checkoutします。タグが事前にpush済みでないと checkout で失敗します。
方法B — タグpushで自動実行:
git tag v1.0.0
git push origin v1.0.0
# → ワークフローが自動起動し、GitHub Releaseに署名済みAPKが添付される- ダウンロード: Actions → ジョブ → Artifacts →
app-release-signed→app-release.apk - 保存期間: 90日間
- Releasesページの GitHub Release にもAPKが自動添付されます
前提: ステップ2のSecretsが登録済みであること。
Java 17以上 と Android SDK(コマンドラインツール・platform・build-tools)が必要です。GitHub Actions のランナーにはプリインストールされていますが、ローカル環境では別途インストールが必要です(Android SDK セットアップガイド):
# デバッグAPK → app/build/outputs/apk/debug/app-debug.apk
./gradlew assembleDebug
# リリースAPK(署名用の環境変数が必要 — 通常はCIが処理)
./gradlew assembleRelease.
├── app/
│ ├── build.gradle.kts # ← アプリID・SDKバージョン・署名設定
│ ├── proguard-rules.pro
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── java/com/yourname/yourapp/
│ │ └── MainActivity.kt
│ └── res/
├── build.gradle.kts # ルートビルド設定
├── settings.gradle.kts
├── gradle.properties
├── gradlew / gradlew.bat
└── .github/workflows/
├── android-ci.yml # push時の自動デバッグビルド
├── release.yml # 署名済みリリースビルド
└── generate-keystore.yml # キーストア生成+Secret登録(初回のみ)
このテンプレートのワークフローはサードパーティのActionをすべてコミットSHAでピン留めしています。
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6@v6 のようなバージョンタグは可変ポインターのため、リポジトリオーナーがいつでも別のコードに差し替えられます。サプライチェーン攻撃やアカウント侵害が起きた場合、@v6 を使っている全プロジェクトで次回実行時に攻撃者のコードが動きます(リポジトリのSecretsに完全アクセスした状態で)。
コミットSHAは不変なので、レビュー済みのコードだけが実行されることを保証できます。コメント(# v6)でバージョンも一目でわかります。
📖 GitHub Actions セキュリティ強化ガイド(GitHub公式)

