Release process¶
This page is the documentation of record for how releases work across every repo in the 7K-Inari org. It covers the automated flow, the per-repo configuration, what you need to do as a contributor, and runbooks for when things go wrong.
The process is recorded as decisions in ADR-0002 (stable releases) and ADR-0015 (release bundle + edge releases).
The flow¶
Every 7K-Inari repo releases the same way:
- Merge conventional commits to
main. Every PR that lands onmainuses Conventional Commits (squash-merge with a conventional title). See Commit message rules. - release-please proposes a Release PR. A workflow running release-please in PR-only mode (
skip-github-release: true) watchesmain. On each push it opens or updates a Release PR containing a version bump and a CHANGELOG diff computed from the conventional commits since the last release. It never creates tags, never creates GitHub Releases, and never triggers CI/publish pipelines. - A maintainer merges the Release PR. This is the only manual step, and it is the release gate: merging the Release PR is cutting the release. Review the proposed version and CHANGELOG before merging.
release.ymlpublishes. A workflow namedrelease.yml, triggeredon: pushtomain, detects the release merge (the release-please version-bump commit) and:- creates the git tag and the GitHub Release (with the CHANGELOG notes), then
- runs the publish jobs — GHCR container images with cosign signatures + SBOM/SLSA provenance, OCI Helm charts/artifacts, goreleaser binaries, Go module and npm tags — either directly or via reusable workflows (
workflow_call).
flowchart LR
A[PR merged to main<br/>conventional commits] --> B[release-please<br/>PR-only mode]
B --> C[Release PR:<br/>version bump + CHANGELOG]
C --> D{Maintainer<br/>merges?}
D -- not yet --> C
D -- merge --> E[release.yml<br/>on: push to main]
E --> F[tag + GitHub Release]
F --> G[Publish jobs]
G --> G1[GHCR images<br/>+ cosign + SBOM/SLSA]
G --> G2[OCI charts/artifacts]
G --> G3[goreleaser binaries]
G --> G4[Go module / npm tags]
Why publish jobs live in release.yml¶
Tags and releases created inside a workflow run using the default GITHUB_TOKEN do not fire downstream workflow triggers. This is a deliberate GitHub anti-recursion rule: events produced by GITHUB_TOKEN never start new workflow runs. If release.yml created the v1.2.3 tag and stopped, a separate on: push: tags: [v*] publish workflow would simply never run.
Therefore the workflow that creates the tag must also run the publish jobs — directly, or by invoking reusable workflows via workflow_call (a workflow_call runs inside the same workflow run, so it is not affected by the rule). Do not split publishing into tag-triggered workflows unless tag creation is moved to a bot PAT/GitHub App token, which we deliberately avoid (see ADR-0002).
Per-repo configuration¶
release-please is configured per repo via release-please-config.json + .release-please-manifest.json. The release-type and publish outputs differ per repo:
| Repo | release-type | Versioned artifact(s) | Publish outputs |
|---|---|---|---|
inari-server |
go |
Container image inari/server |
GHCR image + cosign + SBOM/SLSA |
inari-agent |
go |
Container image inari/agent + install manifests |
GHCR image + cosign + SBOM/SLSA |
inari-operator |
go |
Container image inari/operator |
GHCR image + cosign + SBOM/SLSA |
inari-ui |
node |
Static bundle (served by inari-server) |
versioned bundle artifact |
inari-api |
go |
Contract packages (protobuf/OpenAPI, generated clients) | Go module tag vX.Y.Z |
inari-plugin-sdk |
go |
Go module | Go module tag vX.Y.Z |
inari-ui-plugin-sdk |
node |
npm package | npm publish + dist-tag |
inari-catalog |
simple (per-package paths) |
OCI artifacts + channels | OCI artifacts to GHCR + channel tags (see below) |
inari-cli |
go |
Binaries | goreleaser: archives, brew/scoop, go install tag |
inari-release-bundle |
helm (per-chart packages) |
Charts (OCI) | OCI chart push to GHCR (see below) |
inari-ext-argocd |
go |
Container image + UI remote | GHCR image + cosign + SBOM/SLSA |
inari-docs |
— | Static site | No stable releases — deployed continuously to GitHub Pages (edge marker prereleases only, see below) |
inari-release-bundle: per-chart scheme¶
Charts version independently. The repo (formerly inari-helm-charts) uses one release-please config with a separate package entry per chart path (charts/inari-platform, charts/inari-server, charts/inari-console), each with release-type: helm. (The in-house charts/dex was retired: tenant-cluster Dex is deployed by the inari-operator from the official dexidp chart as ArgoCD Applications — see ADR-0012.)
- Tags are per chart:
<chart-name>-v<version>(e.g.inari-platform-v0.4.1). - The Release PR bumps each changed chart's
versionin itsChart.yamland updates the per-chart CHANGELOG; only charts with releasable commits get bumped. - On merge,
release.yamlcreates the per-chart tags and pushes each changed chart as an OCI artifact to the org-level namespaceoci://ghcr.io/7k-inari/charts/<chart-name>— the same namespace the inari-agent and inari-operator charts (which stay in their repos) publish to. The old repo-scoped namespaces (…/inari-helm-charts/charts,…/inari-server/charts,…/inari-ui/charts) are deprecated; existing artifacts stay published. inari-server/inari-consoleappVersion sync: the charts moved here from the component repos. After their publish pipelines succeed, the inari-server/inari-uirelease.ymldispatchesappversion-bumpto the bundle repo'schart-sync.yml, which commitsfix(<chart>): bump appVersion to vX.Y.Z(plus inari-console'svalues.yamlbundle.tag) so the Release PR proposes a chart patch release and the chart publishes in lockstep with the component.appVersion:in those charts is owned by this sync — never hand-edit it and never annotate itx-release-please-version. Repair path:chart-sync.ymlworkflow_dispatchwithchart+version.- Agent compatibility range:
charts/inari-platformdeclaresagent.supportedRange(human-maintained semver range) andagent.recommended(default pin, auto-bumpable by the inari-agent release pipeline), rendered into theinari-agent-compatConfigMap. inari-server reads these to recommend agent installs/upgrades.
inari-catalog: per-package tag scheme¶
The catalog is a content monorepo (KRO RGDs, platform-app charts, policy packs) with its own, faster cadence.
- release-please runs with a package entry per package directory; tags follow the monorepo scheme
<package-path>/v<version>(e.g.packages/postgres/v1.3.0). - On merge,
release.ymlpushes the changed packages as signed OCI artifacts to GHCR and updates the moving channel tags (stable,incubating) for packages whose channel changed. - Channel membership is metadata in the package, not a git tag — moving a package between channels is a normal conventional commit.
Edge releases¶
Every inari repo except 7k-app-of-apps cuts an edge release on every merge to main (workflow: .github/workflows/edge.yml; skipped when the push is itself a release merge, which the stable pipeline covers). Edge releases make every merge deployable and referenceable without waiting for the human-gated stable release.
- Version scheme:
<pending-version>-<shortsha>(e.g.3.1.0-a1b2c3d), where the pending version comes from the open Release PR's bumped manifest — resolved byscripts/resolve-edge-version.sh(identical copy in every repo; fallback: manifest or latest stable tag + patch; unversioned repos start at0.1.0). - Outputs per merge: an immutable GitHub prerelease
vX.Y.Z-<sha>(never a full release, so "latest release" pointers and release-please's last-release detection stay on stable releases), the commit's artifacts tagged with the sameX.Y.Z-<sha>version, and the movingedgechannel tag/release — a git tagedgeforce-moved to the merge commit whose GitHub prerelease always describes the latest edge build. Moving:edgeartifact tags are kept as well. - Prereleases are never stable: do not pin edge tags in production gitops; they are for e2e, dogfooding, and contract sync between releases.
Per-repo edge artifacts:
| Repo | Edge artifacts (in addition to the prerelease) |
|---|---|
inari-server |
Image :<pending>-<sha> (+ :edge, :edge-<sha>); OpenAPI spec OCI :<pending>-<sha> (cosign-signed) |
inari-ui |
Bundle OCI inari-ui-bundle:<pending>-<sha> (+ :edge) |
inari-agent |
Image :<pending>-<sha> (+ :edge, :edge-<sha>) |
inari-operator |
Image :<pending>-<sha>; both charts pushed at their pending edge versions |
inari-ext-argocd |
Backend image :<pending>-<sha>; backend + UI (ui-v…) prereleases, UI remote assets attached |
inari-api |
npm @7k-inari/api-client@<pending>-<sha> (dist-tag edge); prerelease tag doubles as Go module ref |
inari-ui-plugin-sdk |
npm @<pending>-<sha> (dist-tag edge) |
inari-plugin-sdk |
Prerelease tag only (Go module ref) |
inari-cli |
goreleaser snapshot archives attached to the prerelease |
inari-catalog |
Each package pushed as <pending>-<sha> OCI (cosign-signed, no channel tags); one consolidated edge-<sha> prerelease |
inari-release-bundle |
Each chart pushed as <pending>-<sha> to oci://ghcr.io/7k-inari/charts; per-chart prereleases |
inari-docs |
Prerelease marker tag only (site deploys continuously) |
Operational notes:
- npm OIDC trusted publishers are bound to the workflow filename — each repo's
edge.ymlmust be registered as a trusted publisher on npmjs.com alongsiderelease.yml. - The edge version script is a deliberate identical copy in 12 repos — when changing it, change the canonical copy in
inari-release-bundle(scripts/resolve-edge-version.sh, with tests inscripts/tests/) and sync it everywhere. - Edge prerelease tags must never be picked up as stable: release-please manifest mode reads versions from the manifest and edge releases are marked prerelease. The scheme was canaried on
inari-ui-plugin-sdk; if a repo's release-please ever mis-detects an edge tag as the last stable release, switch that repo to non-v-prefixed edge tags (edge/X.Y.Z-sha).
Contributor guide¶
Commit message rules¶
All commits landing on main must follow Conventional Commits:
Common types: feat, fix, perf, refactor, docs, chore, ci, test, build. PRs are squash-merged — make the PR title the conventional commit message.
Version proposal semantics¶
release-please computes the next version from commit types since the last tag:
| Commit | Effect on proposed version |
|---|---|
feat: |
minor bump (e.g. 1.2.3 → 1.3.0) |
fix:, perf: |
patch bump (1.2.3 → 1.2.4) |
feat!: / fix!: / any type with ! or a BREAKING CHANGE: footer |
major bump (1.2.3 → 2.0.0) |
docs:, chore:, ci:, test:, refactor: (non-breaking) |
no release by themselves |
Release-As: X.Y.Z footer |
forces the proposed version to X.Y.Z |
Pre-1.0 repos: majors become minors and minors become patches per release-please's bump-minor-pre-major / bump-patch-for-minor-pre-major behavior.
Cutting a release¶
- Check the open Release PR (titled like
chore(main): release 1.3.0) in the repo. It refreshes automatically as commits land. - Review the proposed version and CHANGELOG. To change the version, push an empty commit with a
Release-As:footer tomain. - Merge the Release PR. That is the release.
- Watch the
release.ymlrun: it creates the tag + GitHub Release and publishes the artifacts.
Hotfix¶
- Land the fix on
mainas a normal PR with afix:(orfix!:if breaking) conventional title. - release-please adds it to the open Release PR (patch bump) — or opens one if none exists.
- Merge the Release PR. No special branching; releases are always cut from
main.
Skipping a release¶
- Don't merge the Release PR. Nothing publishes until it merges; it simply accumulates more commits.
- To discard a proposal entirely, close the Release PR and delete its branch — release-please will re-propose from scratch when the next releasable commit lands.
- To keep commits out of any release notes/version math, use non-releasing types (
docs:,chore:,ci:) for them.
Runbook¶
release.yml failed after the Release PR merged¶
- Open the failed run and identify which step failed: tag/release creation, or a publish job.
- If the tag was not created: fix the cause, then re-run the failed workflow from the Actions tab (Re-run failed jobs). The workflow is idempotent — it re-detects the release merge and creates the tag.
- If the tag was created but a publish job failed: either re-run failed jobs (preferred), or delete the tag and GitHub Release (
git push origin :refs/tags/vX.Y.Z+ delete the release in the UI) and re-run the whole workflow to recreate both. - If the failure is systemic (registry outage, expired token), fix it first — re-running against the same broken condition just fails again. See Rotating tokens for credential issues.
Reverting a release¶
Published artifacts are not deleted — images, charts, and npm versions already consumed by users stay available. Reverting means superseding:
git revertthe offending change(s) on a branch; open a PR titledfix: revert <change>(orfix!:if the revert itself breaks compat).- Merge it; release-please proposes a new patch/minor release containing the revert.
- Merge the new Release PR. Users upgrade forward to the fixed version.
- If an artifact is actively harmful (e.g. leaked secret), additionally delete the specific GHCR package version / mark the npm version deprecated, and note it in the GitHub Release.
Do not revert the release-please bump commit itself to "unrelease" — the tag and artifacts already exist; history and reality would diverge.
Rotating tokens¶
- Publish jobs use the workflow's
GITHUB_TOKENfor GHCR, tags, and releases — no rotation needed beyond the repo'spermissions:blocks. - Cross-repo work uses
RELEASE_PLEASE_TOKEN(GitHub App or PAT): inari-server/inari-ui dispatchappversion-bumptoinari-release-bundlewith it, and the bundle repo'schart-sync.ymlpushes with it (aGITHUB_TOKENpush would not re-trigger release-please). It needscontents:write+actions:writeoninari-release-bundle. Rotating it means updating the secret in every repo that references it. - Any other repo that needs a PAT or GitHub App token (e.g. cross-repo pushes) stores it as an org or repo Actions secret (see the repo's
release.ymlsecrets:references for exact names). - Rotation: generate the new token with the same scopes → update the secret value in GitHub → re-run the most recent failed
release.ymljob to confirm → revoke the old token. - After rotating, check the next scheduled workflow run (if any) — secret updates do not retroactively fix queued runs.