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:
- Created as an organization GitHub App with
contents:writeandpull_requests:writepermissions - Installed on both the app repo's org and the canonical repo
- Configured with org-level secrets:
APX_APP_ID— the GitHub App's numeric IDAPX_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:
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+repositoriesfields - 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.yamlhas correctorg,source.repo, andapi.id -
.github/workflows/apx-release.ymlexists with correctownerandrepositories - GitHub App is installed on the canonical repo's org
-
APX_APP_IDandAPX_APP_PRIVATE_KEYare set as org secrets - Branch protection on canonical repo requires the
validatestatus 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:
- App repo CI validates and opens the PR
- Canonical CI validates the PR contents
- 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_IDandAPX_APP_PRIVATE_KEYmust be org-level secrets accessible to the repo- The GitHub App must be installed on the
ownerorganization specified in the workflow - The
repositoriesfield must list the canonical repo name (not the full URL)
Release step fails with "permission denied"¶
- The GitHub App needs
contents:writeandpull_requests:writeon 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:
Next Steps¶
- Release Workflow — manual vs CI release paths
- CI Templates — canonical repo workflow details
- Protection — branch/tag protection and GitHub App setup
- Layout — where the workflow file lives in the repo structure