Skip to content

CI Integration

This page covers how to set up continuous integration in an app repository — the workflow file, authentication, prerequisites, and customization options.

Overview

App repos use a single GitHub Actions workflow (apx-release.yml) that triggers on tag push. It validates schemas locally and releases to the canonical repository via a pull request.

Developer pushes tag
┌──────────────────────────────────────┐
│   apx-release.yml  (app repo CI)    │
│                                      │
│  1. Generate GitHub App token        │
│  2. Checkout code (full history)     │
│  3. Install APX                      │
│  4. Parse tag → API ID + version     │
│  5. apx lint                         │
│  6. apx breaking --against HEAD^     │
│  7. apx release prepare + submit     │
│     └─► Opens PR on canonical repo   │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│   ci.yml  (canonical repo CI)        │
│                                      │
│  8. apx lint (re-validates)          │
│  9. apx breaking --against main      │
│  10. Reviewer approves PR            │
│  11. Merge → on-merge.yml runs       │
│      └─► catalog update + tagging    │
└──────────────────────────────────────┘

Prerequisites

APX GitHub App

The CI workflow authenticates via a GitHub App to open PRs on the canonical repository. The app must be:

  1. Created as an organization GitHub App with contents:write and pull_requests:write permissions
  2. Installed on both the app repo's org and the canonical repo
  3. Configured with org-level secrets:
  4. APX_APP_ID — the GitHub App's numeric ID
  5. APX_APP_PRIVATE_KEY — the PEM-encoded private key

See Protection for detailed GitHub App setup.

Workflow File

The workflow file must exist at .github/workflows/apx-release.yml. It is generated automatically during initialization or can be regenerated:

# Generated during app repo setup
apx init app --org=<org> --repo=<app-repo> internal/apis/proto/payments/ledger

# Or regenerated from latest templates
apx workflows sync

The Workflow: apx-release.yml

name: APX Release

on:
  push:
    tags:
      - "proto/**/v[0-9]*"
      - "openapi/**/v[0-9]*"
      - "avro/**/v[0-9]*"
      - "jsonschema/**/v[0-9]*"
      - "parquet/**/v[0-9]*"

permissions:
  contents: read

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Generate App Token
        id: app-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.APX_APP_ID }}
          private-key: ${{ secrets.APX_APP_PRIVATE_KEY }}
          owner: <org>
          repositories: <canonical-repo>

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install APX
        uses: infobloxopen/apx@main

      - name: Parse tag
        id: tag
        run: |
          TAG="${GITHUB_REF#refs/tags/}"
          echo "tag=${TAG}" >> "$GITHUB_OUTPUT"

      - name: Validate
        run: |
          apx lint
          apx breaking --against HEAD^ || true

      - name: Release to canonical repo
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          apx release prepare \
            --tag="${{ steps.tag.outputs.tag }}" \
            --canonical-repo=github.com/<org>/<canonical-repo>
          apx release submit

The <org> and <canonical-repo> placeholders are filled from your apx.yaml when the workflow is generated by apx init app or apx workflows sync.

Step Breakdown

Step Purpose
Generate App Token Creates a short-lived token scoped to the canonical repo using the GitHub App credentials. The owner and repositories fields restrict the token to just the canonical repo.
Checkout Clones the app repo with full history (fetch-depth: 0) so apx breaking can compare against prior commits.
Install APX Downloads the APX CLI binary via the infobloxopen/apx action.
Parse tag Extracts the tag from GITHUB_REF (e.g. proto/payments/ledger/v1/v1.0.0) and passes it to the release step.
Validate Runs apx lint (must pass) and apx breaking (soft failure — || true — since some breaking changes are intentional for new version lines).
Release apx release prepare validates and creates a release manifest, then apx release submit clones the canonical repo, creates a release branch, copies schema files, generates go.mod, and opens a PR.

Tag Patterns

The workflow triggers on tags matching APX's format-prefixed pattern:

<format>/<domain>/<name>/<line>/v<semver>

Examples:

# Protobuf
git tag proto/payments/ledger/v1/v1.0.0
git push origin proto/payments/ledger/v1/v1.0.0

# OpenAPI
git tag openapi/billing/invoices/v2/v2.1.0-beta.1
git push origin openapi/billing/invoices/v2/v2.1.0-beta.1

# Avro
git tag avro/events/clicks/v1/v1.0.0-alpha.3
git push origin avro/events/clicks/v1/v1.0.0-alpha.3

The glob patterns in the workflow (proto/**/v[0-9]* etc.) match any nesting depth, so proto/a/b/v1/v1.0.0 works just as well as proto/a/v1/v1.0.0.


Authentication Flow

┌─────────────────────┐      ┌──────────────────────────┐
│   GitHub Secrets     │      │   Canonical Repo          │
│                      │      │                           │
│  APX_APP_ID ─────────┼──►   create-github-app-token    │
│  APX_APP_PRIVATE_KEY─┼──►   scoped to: owner + repos   │
│                      │      │         │                 │
└─────────────────────┘      │         ▼                 │
                              │   short-lived token       │
                              │   (contents:write,        │
                              │    pull_requests:write)    │
                              │         │                 │
                              │         ▼                 │
                              │   apx release uses token  │
                              │   to push branch + open PR│
                              └──────────────────────────┘

Key details:

  • The token is short-lived (expires after the workflow run)
  • It is scoped to only the canonical repo via the owner + repositories fields
  • The GitHub App must be installed on the org owning the canonical repo
  • Secrets are configured at the organization level so all app repos share them

Updating Workflows

When APX releases a new version with updated workflow templates, regenerate:

# Preview changes
apx workflows sync --dry-run

# Apply
apx workflows sync

# Review and commit
git diff .github/workflows/
git add .github/workflows/ && git commit -m "chore: sync APX workflows"

apx workflows sync detects the repository type automatically: - If .github/workflows/apx-release.yml exists → app repo → regenerates apx-release.yml - If .github/workflows/ci.yml exists → canonical repo → regenerates ci.yml and on-merge.yml - Falls back to checking for canonical directory structure (proto/, openapi/, catalog/) or module_roots in apx.yaml


Adding Custom Steps

You can extend the generated workflow with additional steps. Common additions:

Run Tests Before Releasing

      - name: Validate
        run: |
          apx lint
          apx breaking --against HEAD^ || true

      # Add your custom steps between validate and release
      - name: Run tests
        run: |
          apx gen go && apx sync
          go test ./...

      - name: Release to canonical repo
        # ...

Notify on Release

      - name: Release to canonical repo
        # ...

      - name: Notify Slack
        if: success()
        uses: slackapi/slack-github-action@v2
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {"text": "Released ${{ steps.tag.outputs.tag }} to canonical repo"}

Multi-Language Generation

      - name: Validate and generate
        run: |
          apx lint
          apx breaking --against HEAD^ || true
          apx gen go && apx sync
          apx gen python
          apx gen java
          go test ./...

Warning

When customizing, keep apx workflows sync in mind. It overwrites the entire file. If you have custom steps, either maintain the file manually or re-add customizations after each sync.


Validating CI Setup

Verify your CI configuration before pushing your first tag:

# Check that apx.yaml is valid
apx config validate

# Verify identity coordinates
apx inspect identity

# Ensure the workflow file exists
ls .github/workflows/apx-release.yml

# Dry-run a release to check connectivity
apx release prepare proto/payments/ledger/v1 --version v1.0.0-alpha.1 --dry-run

Checklist

  • apx.yaml has correct org, source.repo, and api.id
  • .github/workflows/apx-release.yml exists with correct owner and repositories
  • GitHub App is installed on the canonical repo's org
  • APX_APP_ID and APX_APP_PRIVATE_KEY are set as org secrets
  • Branch protection on canonical repo requires the validate status check
  • Tag protection on canonical repo restricts proto/**/v* etc. to CI

CI Workflow Lifecycle

Event What runs Where
Push tag to app repo apx-release.yml App repo
PR opened on canonical ci.yml Canonical repo
PR merged to main on-merge.yml Canonical repo

The three workflows form a chain:

  1. App repo CI validates and opens the PR
  2. Canonical CI validates the PR contents
  3. Post-merge CI catalogs the release and creates the official git tag

See CI Templates for details on the canonical repo workflows.


Troubleshooting

Workflow doesn't trigger

  • Tag format: ensure the tag matches <format>/**/v[0-9]* (e.g. proto/payments/ledger/v1/v1.0.0)
  • Workflow file: must be on the default branch (main) before tag push
  • GitHub Actions: check that Actions are enabled for the repository

Token generation fails

  • APX_APP_ID and APX_APP_PRIVATE_KEY must be org-level secrets accessible to the repo
  • The GitHub App must be installed on the owner organization specified in the workflow
  • The repositories field must list the canonical repo name (not the full URL)

Release step fails with "permission denied"

  • The GitHub App needs contents:write and pull_requests:write on the canonical repo
  • Verify the app installation covers the canonical repo

Breaking check fails unexpectedly

The || true after apx breaking makes it advisory. If you want strict breaking-change enforcement, remove || true:

      - name: Validate
        run: |
          apx lint
          apx breaking --against HEAD^

Next Steps