App Repo Release Workflow¶
This page covers how to release schemas from an app repository to the canonical repository. APX provides a multi-step release pipeline and supports both manual and CI-triggered workflows.
Overview¶
Releasing moves a schema snapshot from your app repo into the canonical repository via a pull request. The canonical repo's CI then validates, tags, and catalogs the release.
┌─────────────────────────┐ ┌──────────────────────────┐
│ App Repository │ │ Canonical Repository │
│ │ │ │
│ 1. Author schemas │ │ │
│ 2. Validate locally │ │ │
│ 3. Tag or run release │ │ │
│ │ PR │ │
│ 4. apx release prepare │ │ │
│ apx release submit ─────────► │ 5. CI validates PR │
│ │ │ 6. Reviewer approves │
│ │ │ 7. Merge to main │
│ │ │ 8. Post-merge CI: │
│ │ │ - catalog update │
│ │ │ - tag creation │
└─────────────────────────┘ └──────────────────────────┘
Release Pipeline (apx release)¶
A multi-step workflow with explicit phases, manifest persistence, and idempotency checks. Works for both local development and CI pipelines.
# Step 1: Validate and create a release manifest
apx release prepare proto/payments/ledger/v1 --version v1.0.0
# Step 2: Push release branch and open PR on canonical repo
apx release submit
# Step 3: Tag, catalog, and emit release record (run by canonical CI)
apx release finalize
What each step does:
| Step | Command | Output |
|---|---|---|
| Prepare | apx release prepare | Validates schemas, lifecycle, version-line compatibility. Writes .apx-release.yaml manifest. |
| Submit | apx release submit | Clones canonical repo, creates branch apx/release/<api-id>/<version>, opens PR. Records PR metadata in manifest. |
| Finalize | apx release finalize | Re-validates, creates annotated tag, updates catalog, emits immutable release record. Run by canonical CI after merge. |
The release pipeline also supports lifecycle promotions:
# Examples
apx release prepare proto/payments/ledger/v1 --version v1.0.0-alpha.1 --lifecycle experimental
apx release prepare proto/payments/ledger/v1 --version v1.0.0-beta.1 --lifecycle beta
apx release prepare proto/payments/ledger/v1 --version v1.0.0 --lifecycle stable
# Preview without releasing
apx release prepare proto/payments/ledger/v1 --version v1.0.0 --dry-run
See Release Commands for full reference.
CI-Triggered Releasing¶
The recommended production workflow uses CI to release automatically when you push a tag.
How It Works¶
- You push a tag matching the APX pattern (e.g.
proto/payments/ledger/v1/v1.0.0) - The app repo's
apx-release.ymlworkflow triggers - CI validates the schema and calls
apx release prepare+apx release submit - A PR is opened on the canonical repo
- Canonical CI validates the PR, reviewers approve, and it merges
- Post-merge CI on the canonical repo creates the official tag and updates the catalog
Tag Format¶
Tags follow the pattern <api-id>/<version>:
proto/payments/ledger/v1/v1.0.0
openapi/billing/invoices/v2/v2.1.0-beta.1
avro/events/clicks/v1/v1.0.0-alpha.3
Triggering a Release¶
# Create and push a tag
git tag proto/payments/ledger/v1/v1.0.0
git push origin proto/payments/ledger/v1/v1.0.0
The apx-release.yml workflow matches tags against these patterns:
on:
push:
tags:
- "proto/**/v[0-9]*"
- "openapi/**/v[0-9]*"
- "avro/**/v[0-9]*"
- "jsonschema/**/v[0-9]*"
- "parquet/**/v[0-9]*"
The App Repo Workflow¶
The apx-release.yml workflow is generated by apx init app or apx workflows sync:
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: Extract API ID and version from tag
id: parse
run: |
TAG="${{ steps.tag.outputs.tag }}"
# Tag format: <api-id>/<version> e.g. proto/payments/ledger/v1/v1.0.0
VERSION="${TAG##*/}" # last component
API_ID="${TAG%/*}" # everything before last /
echo "api_id=${API_ID}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Release to canonical repo
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
apx release prepare "${{ steps.parse.outputs.api_id }}" \
--version "${{ steps.parse.outputs.version }}" \
--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.
Manual vs CI Releasing¶
| Aspect | Manual (apx release prepare + submit) | CI (tag-triggered) |
|---|---|---|
| Trigger | Run from terminal | Push a git tag |
| Auth | Your gh CLI session | GitHub App token |
| Validation | Local only | CI runs lint + breaking |
| Audit trail | Git log | CI run URL in PR body |
| Best for | Development, quick iteration | Production releases |
Both paths produce the same result: a PR on the canonical repo with the schema snapshot.
What Happens After the PR¶
Once the PR is opened on the canonical repo (by either path):
- Canonical CI validates —
ci.ymlrunsapx lintandapx breaking --against origin/main - Review — team members review the schema changes
- Merge — PR merges to
main - Post-merge CI —
on-merge.ymlruns: - Re-validates schemas
- Runs
apx catalog generateto updatecatalog/catalog.yaml - Commits catalog changes with
[skip ci]
See CI Templates for details on canonical repo workflows.
Prerequisites¶
For Manual Releasing¶
ghCLI installed and authenticated (gh auth login)- Write access to the canonical repo (push branches + create PRs)
For CI Releasing¶
- APX GitHub App installed on both the app repo and canonical repo
- Org secrets
APX_APP_IDandAPX_APP_PRIVATE_KEYavailable to the app repo apx-release.ymlworkflow in.github/workflows/
See Protection for GitHub App setup.
Lifecycle and Version Rules¶
APX enforces consistency between lifecycle state and version:
| Lifecycle | Required version format | Example |
|---|---|---|
experimental | -alpha.* prerelease | v1.0.0-alpha.1 |
beta | -alpha.*, -beta.*, or -rc.* | v1.0.0-beta.1 |
stable | No prerelease tag | v1.0.0 |
deprecated | Any | v1.2.1 |
sunset | Blocked (no new releases) | — |
# These work:
apx release prepare proto/payments/ledger/v1 --version v1.0.0-alpha.1 --lifecycle experimental
apx release prepare proto/payments/ledger/v1 --version v1.0.0 --lifecycle stable
# This fails (stable requires clean semver):
apx release prepare proto/payments/ledger/v1 --version v1.0.0-beta.1 --lifecycle stable
See Release Overview for the full lifecycle model.
Troubleshooting¶
apx release prepare fails with "gh: not authenticated"¶
Run gh auth login to authenticate the GitHub CLI.
PR creation fails with permission error¶
Ensure you have push access to the canonical repo, or use the CI-triggered path with the GitHub App token.
"version already released" error¶
The same version with identical content was already released. This is an idempotency check — apx release prepare detects the existing tag by comparing SHA-256 content hashes and reports success.
CI workflow doesn't trigger on tag push¶
Verify the tag matches the expected pattern. Tags must start with the schema format prefix:
# Correct
git tag proto/payments/ledger/v1/v1.0.0
# Wrong — missing the version line directory
git tag proto/payments/ledger/v1.0.0
Next Steps¶
- CI Integration — app repo CI setup details
- Release Commands — multi-step pipeline
- CI Templates — canonical repo workflows