CI automation with GitHub Actions

This workflow builds your app, uploads a bundle, and can either leave it uploaded, release it to the base channel, or release it to a named channel.

1. Add repository secrets and variables

GitHub repository secrets:

OTAKIT_SECRET_KEY=otakit_sk_...
OTAKIT_APP_ID=app_...

Optional GitHub repository variables:

OTAKIT_RELEASE_CHANNEL=base

# Leave empty or unset for upload-only.
# Use "base" for the base channel.
# Use a channel name like "staging" for a named release track.

2. Copy this workflow

Create .github/workflows/otakit.yml:

name: OTA upload

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

jobs:
  upload:
    runs-on: ubuntu-latest
    env:
      OTAKIT_SECRET_KEY: ${{ secrets.OTAKIT_SECRET_KEY }}
      OTAKIT_APP_ID: ${{ secrets.OTAKIT_APP_ID }}
      OTAKIT_RELEASE_CHANNEL: ${{ vars.OTAKIT_RELEASE_CHANNEL }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci
      - run: npm run build

      - name: Upload bundle
        run: |
          if [ -z "$OTAKIT_RELEASE_CHANNEL" ]; then
            npx --yes @otakit/cli@latest upload
          elif [ "$OTAKIT_RELEASE_CHANNEL" = "base" ]; then
            npx --yes @otakit/cli@latest upload --release
          else
            npx --yes @otakit/cli@latest upload --release "$OTAKIT_RELEASE_CHANNEL"
          fi

This workflow uses the default bundle path from capacitor.config.ts.

Release channel options

  • Empty or unset OTAKIT_RELEASE_CHANNEL: upload only
  • OTAKIT_RELEASE_CHANNEL=base: upload and release to the base channel
  • OTAKIT_RELEASE_CHANNEL=staging: upload and release to the staging channel

Self-hosted addition

If you run your own OtaKit server, add this environment variable to the workflow as well:

OTAKIT_SERVER_URL: ${{ secrets.OTAKIT_SERVER_URL }}

Best practices

  • Pin the CLI version after your first successful run instead of using @latest forever.
  • Use the base channel for the simplest production setup. Add a named channel only when you need a separate rollout track such as staging.
  • Use branch protection so OTA uploads only run from trusted branches.

Related docs: CLI reference and channels.