Skip to content

GitHub Actions

Automate your iOS and Android builds directly from your GitHub repository. With one workflow file and a handful of repository secrets, every push, tag, or manual trigger can produce signed, store-ready apps — without anyone on the team needing a Mac, Xcode, or Android Studio installed.

Hands-off Releases

Tag a release in Git and your signed iOS and Android binaries are submitted to TestFlight and Play Store automatically.

No Local Setup

Contributors on Windows or Linux can trigger iOS builds. No Xcode, no provisioning hassles, no shared signing certificates floating around laptops.

Scoped Secrets

Credentials live in GitHub repository secrets, scoped to your repo and visible only to the workflow runner. Easy to rotate, easy to audit.

Parallel Builds

Build iOS and Android at the same time with a matrix job. A typical release finishes in under 10 minutes.

Before setting up the workflow, make sure you have:

  • A Capgo account with an active subscription and a Capgo API key
  • Your app registered in Capgo (bunx @capgo/cli@latest app add if not)
  • Build credentials configured locally with bunx @capgo/cli@latest build init — see Managing Credentials for the wizard walkthrough
  • A successful local build (bunx @capgo/cli@latest build com.example.app --platform android --build-mode debug) — CI is not the place to debug your first build
  • The GitHub CLI (gh) installed and authenticated (gh auth login)

The Capgo CLI can export your local credentials as a ready-to-use .env file. Combined with gh secret set -f, this turns the entire CI/CD setup into three commands — no manual base64 encoding, no JSON wrangling, no copy-paste-secret-by-secret.

  1. Add your Capgo API key as a repository secret

    The API key isn’t part of the per-app credential store, so add it once manually:

    Terminal window
    gh secret set CAPGO_TOKEN --body "your_capgo_api_key_here"

    Generate the key in the Capgo dashboard with upload permissions or higher.

  2. Export your credentials to a .env file

    Run the interactive credentials manager:

    Terminal window
    bunx @capgo/cli@latest build credentials manage --appId com.example.app

    In the TUI, select Export to .env. The CLI writes .env.capgo.<appId> to your current directory with mode 0600 (owner-readable only) — for example, .env.capgo.com.example.app. When both iOS and Android are configured, both platforms’ secrets land in the same file under # === IOS === and # === ANDROID === section headers. iOS and Android env-var names are disjoint, so combining them is conflict-free.

  3. Push the .env file to GitHub Actions secrets

    The gh secret set -f command reads a dotenv file and creates one repository secret per KEY=value line:

    Terminal window
    gh secret set -f .env.capgo.com.example.app

    That’s it — every secret your workflow needs is now in GitHub. Verify with gh secret list.

  4. Create the workflow file

    Add .github/workflows/capgo-build.yml to your repository. Pick one of the three trigger patterns below depending on how you want to fire builds.

For reference, gh secret set -f will create these repository secrets (your workflow YAML references them by these exact names):

PlatformSecrets created
iOSBUILD_CERTIFICATE_BASE64, P12_PASSWORD, CAPGO_IOS_PROVISIONING_MAP_BASE64, APPLE_KEY_ID, APPLE_ISSUER_ID, APPLE_KEY_CONTENT, APP_STORE_CONNECT_TEAM_ID
AndroidANDROID_KEYSTORE_FILE, KEYSTORE_KEY_ALIAS, KEYSTORE_KEY_PASSWORD, KEYSTORE_STORE_PASSWORD, PLAY_CONFIG_JSON
(added manually)CAPGO_TOKEN

You don’t need to memorise these — the workflow examples below already reference all of them.

The three examples below cover the most common patterns. They all use the same shape: check out the repo, install dependencies, build the web assets, sync to native, then call Capgo Build with credentials passed as environment variables.

Lets anyone with write access fire a build from the Actions tab in GitHub with a platform dropdown. Useful for ad-hoc test builds or kicking off a release on demand.

.github/workflows/capgo-build-manual.yml
name: Capgo Build (Manual)
on:
workflow_dispatch:
inputs:
platform:
description: 'Platform to build'
required: true
default: 'android'
type: choice
options: [ios, android, both]
mode:
description: 'Build mode'
required: true
default: 'debug'
type: choice
options: [debug, release]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run build
- run: bunx cap sync
- name: Trigger Capgo Build
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
CAPGO_IOS_PROVISIONING_MAP_BASE64: ${{ secrets.CAPGO_IOS_PROVISIONING_MAP_BASE64 }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }}
KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }}
KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
PLAY_CONFIG_JSON: ${{ secrets.PLAY_CONFIG_JSON }}
run: |
bunx @capgo/cli@latest build com.example.app \
--platform ${{ inputs.platform }} \
--build-mode ${{ inputs.mode }}

Replace com.example.app with your app ID. Once committed, go to Actions → Capgo Build (Manual) → Run workflow to trigger it.

Builds and ships both platforms in parallel whenever you push a version tag like v1.4.0. This is the most common production setup — git tag v1.4.0 && git push --tags becomes your release command.

.github/workflows/capgo-build-release.yml
name: Capgo Build (Release)
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform: [ios, android]
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run build
- run: bunx cap sync ${{ matrix.platform }}
- name: Build ${{ matrix.platform }}
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
# iOS
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
CAPGO_IOS_PROVISIONING_MAP_BASE64: ${{ secrets.CAPGO_IOS_PROVISIONING_MAP_BASE64 }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
# Android
ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }}
KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }}
KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
PLAY_CONFIG_JSON: ${{ secrets.PLAY_CONFIG_JSON }}
run: |
bunx @capgo/cli@latest build com.example.app \
--platform ${{ matrix.platform }} \
--build-mode release

The matrix runs iOS and Android in parallel on separate runners. Setting fail-fast: false means a failed iOS build won’t cancel the in-progress Android build (and vice versa) — useful when one platform has a transient signing issue.

Catches native build regressions early by producing a debug Android build on every push to main. Cheap to run, fast feedback, and you can skip Play Store upload to keep it purely a smoke test.

.github/workflows/capgo-build-main.yml
name: Capgo Build (Main)
on:
push:
branches: [main]
paths:
- 'src/**'
- 'android/**'
- 'ios/**'
- 'package.json'
- 'capacitor.config.*'
jobs:
smoke-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run build
- run: bunx cap sync android
- name: Smoke build (Android debug)
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }}
KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }}
KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
run: |
bunx @capgo/cli@latest build com.example.app \
--platform android \
--build-mode debug \
--no-playstore-upload \
--output-upload

The paths filter ensures the workflow doesn’t run on doc-only changes. --no-playstore-upload skips Play Store submission (no PLAY_CONFIG_JSON needed), and --output-upload produces a download URL for the resulting APK so you can install it on a test device.

For test builds, use --no-playstore-upload (Android) or --no-app-store-upload (iOS) to skip store submission. Combine with --output-upload to get a time-limited download URL for the binary.

Pass --output-record <path> to persist the build artifact URL and QR code to disk when the build succeeds, then read it back in subsequent steps with build last-output. No log scraping, no regex.

- name: Build
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
# ...credentials...
run: |
bunx @capgo/cli@latest build com.example.app \
--platform android --build-mode debug \
--output-upload --output-retention 1d \
--output-record /tmp/build.json
- name: Comment on PR with build URL
env:
GH_TOKEN: ${{ github.token }}
run: |
URL=$(bunx @capgo/cli@latest build last-output --path /tmp/build.json --field outputUrl)
if [ -n "$URL" ]; then
gh pr comment ${{ github.event.pull_request.number }} \
--body "Debug build ready: $URL"
fi

--output-record /tmp/build.json writes a JSON record (with jobId, status, outputUrl, qrCodeAscii, qrCodePngPath, finishedAt) and a PNG QR code alongside at /tmp/build.json.qr.png. build last-output reads it back:

  • --field outputUrl prints just the download URL (newline-terminated; safe for URL=$(...)).
  • --field qrCodePngPath prints the PNG path so you can upload it as a PR attachment.
  • --qr prints the rendered ASCII QR — drop it inside a Markdown code fence on the PR comment for inline scannability.

By default each release build increments the build number. To pin it to a value you control (for example, the Git tag), pass --skip-build-number-bump:

- name: Set version from tag
run: |
VERSION="${GITHUB_REF#refs/tags/v}"
# Update package.json or your version source here
bun pm version "$VERSION" --no-git-tag-version
- name: Build
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
# ...credentials...
run: |
bunx @capgo/cli@latest build com.example.app \
--platform ios --build-mode release \
--skip-build-number-bump

bun install is already fast enough that a JS-deps cache rarely pays off, but Capacitor’s native dependencies (CocoaPods, Gradle) are worth caching for larger projects:

- uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
ios/App/Pods
android/.gradle
key: ${{ runner.os }}-capgo-${{ hashFiles('**/bun.lock', '**/Podfile.lock') }}
SymptomLikely cause
CAPGO_TOKEN is not setSecret not added, or job has no access to it (check environment/branch protections)
Missing iOS / Android credential errorsgh secret set -f was not run, or was run against a different repo. Verify with gh secret list
cap sync fails in CI but works locallyA native plugin isn’t in package.json, or you forgot bun install before cap sync
Build succeeds but no app appears in App Store ConnectWrong team ID, or the app record doesn’t exist yet in App Store Connect. Verify locally with bunx @capgo/cli build credentials manage
Build hangs after “Uploading project”Project archive is unusually large — check that node_modules isn’t being uploaded (it shouldn’t be by default)
Provisioning profile doesn't match bundle IDThe provisioning map points at a different bundle ID than the one Xcode is signing. Re-run build init to refresh the profile, then re-export with build credentials manage
Credentials changed locally but CI still failsDon’t forget to re-export and re-push: bunx @capgo/cli build credentials manage → gh secret set -f .env.capgo.<appId>
Manager refuses to write the combined fileShared config keys differ between platforms — the manager warns and asks for confirmation. Either confirm to overwrite-one-wins, or re-export per-platform with --platform ios / --platform android
build last-output prints an empty URLThe build did not pass --output-upload, or it failed before producing an artifact. outputUrl will be null in the record. Branch on [ -n "$URL" ] before using it
build last-output errors with Unsupported record schemaVersionThe runner is on an older CLI than the one that wrote the record. Pin both producer and reader to the same explicit version (e.g. bunx @capgo/cli@7.104.0 … on both sides) rather than @latest, which floats and can drift between jobs

For platform-specific build failures, see the Troubleshooting guide.