gh-actions-expo-apk-build

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

GitHub Actions: Build Expo Android APK/AAB

GitHub Actions:构建Expo Android APK/AAB

Build a release Android APK for an Expo (React Native) app on GitHub Actions without paying for or waiting on EAS cloud. Proven on Expo SDK 57 / RN 0.86 with react-native-google-mobile-ads.
无需付费或等待EAS云服务,在GitHub Actions上为Expo(React Native)应用构建正式版Android APK。已在Expo SDK 57 / RN 0.86搭配react-native-google-mobile-ads环境下验证可行。

When to use (vs EAS)

适用场景(对比EAS)

SituationUse
Test APK on a real device, free, no token, auto on every pushThis skill (GH Actions)
EAS free tier stuck
IN_QUEUE
for 30+ min
This skill (GH Actions)
Upload to Play Store (AAB, proper signing, Play App Signing)EAS Build
production
profile
Need iOS buildEAS Build (GH Actions has no macOS here)
APK built by this workflow is signed with the debug keystore by default — installable on devices. For a release-signed APK/AAB (needed for Play Console), pass the release keystore + signing plugin: see the
android-release-signing
skill (proven: GH Actions can produce release-signed artifacts without EAS).
场景选择方案
在真实设备上测试APK,免费、无需令牌、每次推送自动构建本技能(GitHub Actions)
EAS免费 tier 排队等待30分钟以上本技能(GitHub Actions)
上传至应用商店(AAB格式、合规签名、Play应用签名)EAS Build
production
配置文件
需要iOS构建EAS Build(GitHub Actions无macOS环境)
本工作流构建的APK默认使用调试密钥库签名——可直接安装到设备上。若需用于Play Console的正式签名APK/AAB,请传入正式密钥库+签名插件:详见
android-release-signing
技能(已验证:GitHub Actions无需EAS即可生成正式签名制品)。

Decision order

决策优先级

  1. APK (test) → workflow below, no secrets needed (debug keystore) or with keystore secrets for a release-signed APK.
  2. AAB (store) → sign with the project release keystore inside the workflow (decode keystore secret →
    expo prebuild
    with signing plugin →
    ./gradlew bundleRelease
    ). This works without EAS; see
    references/aab-play-store-signing.md
    and the
    android-release-signing
    skill. (EAS Build is an alternative if you prefer EAS-managed signing.)
  3. Build failing with a Kotlin/version error → read
    references/troubleshooting.md
    FIRST. These are the 5-fix failure modes that cost hours.
  1. APK(测试用)→ 使用下方工作流,无需密钥(调试密钥库)或传入密钥库密钥生成正式签名APK。
  2. AAB(应用商店用)→ 在工作流内使用项目正式密钥库签名(解码密钥库密钥 → 搭配签名插件执行
    expo prebuild
    → 运行
    ./gradlew bundleRelease
    )。无需EAS即可实现;详见
    references/aab-play-store-signing.md
    android-release-signing
    技能。(若偏好EAS托管签名,EAS Build是替代方案。)
  3. 构建因Kotlin/版本错误失败 → 先阅读
    references/troubleshooting.md
    。这些问题有5种修复模式,曾耗费数小时排查。

Procedure (APK, test build)

操作步骤(APK测试构建)

1. Preconditions

1. 前置条件

  • Repo is pushed to GitHub (workflow only runs there).
  • google-services.json
    (if the app uses Firebase/AdMob) is committed.
  • android/
    is NOT committed (gitignored) — CI regenerates it via
    expo prebuild
    .
  • Default branch is
    main
    (rename
    master
    first:
    git branch -M main
    ) or adjust the trigger.
  • 代码仓库已推送到GitHub(工作流仅在此运行)。
  • google-services.json
    (若应用使用Firebase/AdMob)已提交到仓库
  • android/
    目录未提交(已加入.gitignore)——CI将通过
    expo prebuild
    重新生成该目录。
  • 默认分支为
    main
    (若原分支为
    master
    ,先执行
    git branch -M main
    重命名)或调整工作流触发分支。

2. Create the workflow

2. 创建工作流

Open
references/workflow-template.md
and write
.github/workflows/build-apk.yml
from the base template. Keep the canonical step order: checkout → Node → JDK 17 → Android SDK →
npm ci
expo prebuild --clean --no-install
(with
CI: 1
) →
./gradlew assembleRelease
upload-artifact
.
  • JDK 17 for Expo SDK 52+ / RN 0.7x+ (21 is not required).
  • Always
    --clean
    on prebuild so
    android/
    matches
    app.json
    (plugins, google-services, proguard).
  • Validate the YAML before pushing (see below).
  • Set
    if-no-files-found: error
    on the upload step.
打开
references/workflow-template.md
,基于基础模板编写
.github/workflows/build-apk.yml
。请遵循标准步骤顺序:checkout → Node → JDK 17 → Android SDK →
npm ci
expo prebuild --clean --no-install
(搭配
CI: 1
环境变量)→
./gradlew assembleRelease
upload-artifact
  • Expo SDK 52+ / RN 0.7x+需使用JDK 17(无需JDK 21)。
  • 执行prebuild时务必添加
    --clean
    参数,确保
    android/
    目录与
    app.json
    (插件、google-services、proguard配置)保持一致。
  • 推送前验证YAML格式(详见下文)。
  • 在上传步骤设置
    if-no-files-found: error

3. Push and monitor

3. 推送并监控构建

bash
git add -A && git commit -m 'ci: add APK build workflow' && git push origin main
gh run list --repo <owner>/<repo> --limit 3
gh run watch <run-id> --repo <owner>/<repo>      # or poll via gh run view --json status,conclusion
gh run view <run-id> --repo <owner>/<repo> --log-failed   # when it fails — read THIS, not the full log
First build takes ~15–25 min (dependency download). Debug CI with
--log-failed
, then
--log | grep <keyword>
for specifics.
bash
git add -A && git commit -m 'ci: add APK build workflow' && git push origin main
gh run list --repo <owner>/<repo> --limit 3
gh run watch <run-id> --repo <owner>/<repo>      # 或通过gh run view --json status,conclusion轮询状态
gh run view <run-id> --repo <owner>/<repo> --log-failed   # 构建失败时查看此日志,而非完整日志
首次构建耗时约15–25分钟(依赖下载)。使用
--log-failed
调试CI,或通过
--log | grep <keyword>
定位具体问题。

4. Download the artifact

4. 下载制品

Artifacts expire after 90 days.
bash
gh run download <run-id> --repo <owner>/<repo> --dir apk --pattern '*.zip'
制品90天后过期。
bash
gh run download <run-id> --repo <owner>/<repo> --dir apk --pattern '*.zip'

or, faster for large artifacts:

大体积制品可使用更快方式:

gh api repos/<owner>/<repo>/actions/artifacts --jq '.artifacts[] | select(.workflow_run.id==<run-id>) | {name, id, size_in_bytes, archive_download_url}' curl -sL -H "Authorization: Bearer $GH_TOKEN" -o apk/artifact.zip '<archive_download_url>' unzip -o -q apk/artifact.zip -d apk && ls -lh apk/*.apk

Keep `GH_TOKEN` in an env var / shell export; never print it.
gh api repos/<owner>/<repo>/actions/artifacts --jq '.artifacts[] | select(.workflow_run.id==<run-id>) | {name, id, size_in_bytes, archive_download_url}' curl -sL -H "Authorization: Bearer $GH_TOKEN" -o apk/artifact.zip '<archive_download_url>' unzip -o -q apk/artifact.zip -d apk && ls -lh apk/*.apk

请将`GH_TOKEN`存入环境变量/shell导出变量;切勿打印该令牌。

5. If the build fails with a compile error

5. 若构建因编译错误失败

Open
references/troubleshooting.md
— the Kotlin-metadata and version-pin section covers the exact failure ladder hit by react-native-google-mobile-ads 16.x on RN 0.86.
打开
references/troubleshooting.md
——其中Kotlin元数据与版本锁定章节涵盖了react-native-google-mobile-ads 16.x在RN 0.86环境下遇到的所有典型失败场景。

6. AAB / Play Store release

6. AAB / 应用商店发布

Open
references/aab-play-store-signing.md
BEFORE building a store release. Two options:
Option A — release keystore in the GH Actions workflow (no EAS, proven). Add keystore secrets (
ANDROID_KEYSTORE_BASE64/PASSWORD/ALIAS/KEY_PASSWORD
), decode them in the workflow, and run
expo prebuild
with a signing config plugin so
bundleRelease
produces a release-signed AAB. Full details + verify steps (jarsigner, not apksigner, for AAB) in the
android-release-signing
skill. Back up the keystore locally — losing it means losing the ability to update the app.
Option B — EAS Build.
npx eas-cli build --platform android --profile production
— EAS generates/manages the upload keystore on its servers. Requires the user's
EXPO_TOKEN
(agent can't run it without it). Steps:
  1. Ensure
    eas.json
    has a
    production
    profile with
    buildType: "app-bundle"
    and
    autoIncrement: true
    .
  2. npx eas-cli build --platform android --profile production
    .
  3. Back up the keystore:
    npx eas-cli credentials --platform android
    → download
    credentials.json
    (gitignore it).
  4. Upload a Play service-account JSON to EAS credentials, then
    npx eas-cli submit --platform android
    .
  5. Never commit keystores.
构建应用商店版本前,请先阅读
references/aab-play-store-signing.md
。有两种方案:
方案A — 在GitHub Actions工作流中使用正式密钥库(无需EAS,已验证) 添加密钥库密钥(
ANDROID_KEYSTORE_BASE64/PASSWORD/ALIAS/KEY_PASSWORD
),在工作流中解码,搭配签名配置插件执行
expo prebuild
,使
bundleRelease
生成正式签名AAB。完整细节+验证步骤(AAB需使用jarsigner而非apksigner)详见
android-release-signing
技能。请在本地备份密钥库——丢失密钥库将无法更新应用。
方案B — EAS Build。执行
npx eas-cli build --platform android --profile production
——EAS将在其服务器上生成/管理上传密钥库。需要用户的
EXPO_TOKEN
(无令牌则无法执行)。步骤如下:
  1. 确保
    eas.json
    包含
    production
    配置文件,其中
    buildType: "app-bundle"
    autoIncrement: true
  2. 执行
    npx eas-cli build --platform android --profile production
  3. 备份密钥库:执行
    npx eas-cli credentials --platform android
    → 下载
    credentials.json
    (加入.gitignore)。
  4. 将Google Play服务账号JSON上传至EAS凭据,然后执行
    npx eas-cli submit --platform android
  5. 切勿提交密钥库到仓库。

Reference routing

参考文档指引

Open when...File
Writing or adjusting the workflow YAML (steps, JDK/Node versions, caching, debug/release variants, secrets)
references/workflow-template.md
Building for the Play Store: AAB, keystore, Play App Signing,
eas submit
, keystore backup/recovery
references/aab-play-store-signing.md
Build fails at
compileReleaseKotlin
/ "incompatible version of Kotlin" / "binary version of its metadata" / any native dependency version conflict
references/troubleshooting.md
Need to pin a transitive Gradle dependency via config plugin
scripts/pin-gradle-dependency.js
(copy into
plugins/
and register in
app.json
)
Update/maintain this skill's contract
SPEC.md
适用场景文档
编写或调整工作流YAML(步骤、JDK/Node版本、缓存、调试/正式变体、密钥)
references/workflow-template.md
应用商店构建:AAB、密钥库、Play应用签名、
eas submit
、密钥库备份/恢复
references/aab-play-store-signing.md
构建在
compileReleaseKotlin
阶段失败 / 提示“Kotlin版本不兼容” / “元数据二进制版本”问题 / 任何原生依赖版本冲突
references/troubleshooting.md
需要通过配置插件锁定Gradle传递依赖
scripts/pin-gradle-dependency.js
(复制到
plugins/
目录并在
app.json
中注册)
更新/维护本技能规范
SPEC.md

Security notes

安全注意事项

  • Never commit keystores or secrets. Debug keystore builds need no secrets.
  • If a token (EXPO_TOKEN, GH_TOKEN/ghp_*) was ever pasted into a chat, tell the user to revoke it after the build finishes.
  • Token in push URL leaks via
    git remote -v
    : push with
    git -c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64)" push origin main
    instead.
  • 切勿提交密钥库或密钥到仓库。调试密钥库构建无需密钥。
  • 若令牌(EXPO_TOKEN、GH_TOKEN/ghp_*)曾粘贴到聊天窗口,请告知用户构建完成后立即撤销该令牌。
  • 推送URL中的令牌会通过
    git remote -v
    泄露:请改用
    git -c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64)" push origin main
    执行推送。

AAB signing essentials

AAB签名要点

  • Upload key vs App Signing key: your keystore (project-managed in the workflow, or EAS-managed) signs the AAB; Google's Play App Signing key re-signs user APKs. They must differ, and losing your upload key is recoverable (Google Play Support reset, with a 72-hour validity window) because Google holds the final key.
  • Back up the keystore immediately after the first production build (
    eas credentials
    → download
    credentials.json
    ); never commit it.
  • eas submit
    needs a Google Play service-account JSON
    (Release Manager permission) uploaded to EAS credentials.
  • 上传密钥 vs 应用签名密钥:你的密钥库(工作流中项目托管或EAS托管)对AAB签名;Google的Play应用签名密钥会重新对用户下载的APK签名。两者必须不同,且丢失上传密钥可恢复(通过Google Play支持重置,有72小时有效期窗口),因为Google持有最终签名密钥。
  • 首次正式构建后立即备份密钥库(执行
    eas credentials
    → 下载
    credentials.json
    );切勿提交到仓库。
  • eas submit
    需要Google Play服务账号JSON
    (需拥有Release Manager权限)并上传至EAS凭据。

Golden rules (learned the hard way)

黄金准则(从实践中总结)

  • Don't bump Kotlin to read newer metadata. Raising
    kotlinVersion
    can break other libs (e.g. react-native-safe-area-context crashes on Kotlin 2.3) and still not fix the module. Pin the offending native dependency DOWN instead.
  • expo-build-properties
    kotlinVersion
    only writes gradle.properties
    — it does NOT override the RN version catalog, so it often has zero effect. Verify in
    android/gradle/libs.versions.toml
    after prebuild.
  • Verify the real cause before patching. Resolve what version Gradle actually picked and inspect the module's Gradle file / AAR metadata; guessing causes repeated failed runs.
  • Config-plugin pins are the durable fix (prebuild regenerates
    android/
    each run); hand-editing
    android/build.gradle
    is wiped on the next prebuild.
  • When a pinned version lacks APIs the module calls, the compiler error names the exact symbol — patch only those call sites via patch-package, with a safe fallback, and verify the patch applies on a pristine copy.
  • 不要为读取新版本元数据升级Kotlin版本。提升
    kotlinVersion
    可能破坏其他库(例如react-native-safe-area-context在Kotlin 2.3环境下崩溃),且仍无法修复问题。应降级锁定出问题的原生依赖版本。
  • expo-build-properties
    kotlinVersion
    仅写入gradle.properties
    ——它不会覆盖RN版本目录,因此通常无效。请在prebuild后查看
    android/gradle/libs.versions.toml
    验证配置。
  • 补丁前先确认真实原因。确定Gradle实际选择的版本,检查模块的Gradle文件/AAR元数据;盲目猜测会导致多次构建失败。
  • 配置插件锁定是持久化修复方案(每次prebuild都会重新生成
    android/
    目录);手动编辑
    android/build.gradle
    会在下一次prebuild时被覆盖。
  • 当锁定的版本缺少模块调用的API时,编译错误会明确指出缺失的符号——仅通过patch-package修补这些调用位置,并添加安全回退,同时验证补丁在纯净环境下可正常应用。