Skip to main content

Release flow

ERun's release flow is repository-wide: a release moves all modules together (erun-cli, erun-common, erun-mcp, erun-ui, erun-devops).

Versioning​

  • erun-devops/VERSION is the canonical product version.
  • Chart version and appVersion are kept in sync with this file at release time.
  • A version is a content identity minted only by erun build: a plain build mints <base>-snapshot-<UTC-timestamp>, and --release (used by erun release) pins the bare semver. The env type does not decide which kind is minted.

Release-tagged images​

erun build --release builds release-tagged images for both linux/amd64 and linux/arm64, then hands the version to erun push, which pushes the per-arch tags and assembles a manifest list so <image>:<version> resolves to either arch automatically. The build does not push directly — it reuses push for every publish, so a released version and a pushed snapshot are published the same way. erun release itself does no build and no publish.

That publish happens before the release tag reaches origin, and the build re-resolves each published manifest afterwards. A completed build --release therefore means the version it announced is deployable; a build that cannot publish fails while the tag is still local. erun release on its own makes no such claim: it marks source control, and its exit 0 says nothing about artifacts.

Base images (erun-ubuntu, erun-dind, erun-ubuntu-derived) publish first; dependent images publish only after their bases are available in the registry.

Published runtime chart​

Chart publishing belongs to erun push, not to a release-only step: push publishes every component's co-located helm chart at the pushed version. It runs helm package + helm push to oci://<registry>/charts, so a chart is addressable as oci://<registry>/charts/<component>:<version> (e.g. erun-devops, erun-powerdns, erun-backend-*, erun-docs; default registry ghcr.io/sophium) — kept separate from the same-named image repo so a chart never collides with its image at the same ref — then verifies each artifact with a helm pull round-trip before the push is considered complete. Chart publishing is decoupled from the image push: a version-pinned base (erun-powerdns at 4.9.3, erun-backend-postgres at 18.3, erun-zitadel/erun-zitadel-login at v4.15.3) keeps its image at the upstream pin and is not re-pushed at the release version, but its chart still publishes at the release version so platform deploys and wrapper charts resolve it. Each chart's version and appVersion equal the pushed version.

Because push does this for every version — snapshot or release — there is no chart-versus-image gap: any pushed version is deployable, not just released ones. erun build --release gets its chart published for free by reusing push; a snapshot pushed during iteration is just as deployable as a release.

Environments without a repo-local runtime chart deploy this artifact directly — see erun deploy · Where the runtime chart comes from.

Build caching​

Release-tagged builds participate in the same content-fingerprint cache as snapshot builds. Fresh clones promote pinned bases without rebuilding; local Dockerfile edits trigger a rebuild because the recomputed fingerprint diverges.

See Conventions · Fingerprint cache for the full mechanism.

Branch model​

erun release operates against two project-configured branches:

FieldDefaultRole
release.mainbranch (.erun/config.yaml)mainThe branch that holds released versions. Release tags are created here.
release.developbranch (.erun/config.yaml)developThe branch where the next development cycle continues. The post-release "bump to next patch version" lands here.

Override either in .erun/config.yaml:

release:
mainbranch: production
developbranch: trunk

The conventional flow on a release:

  1. CI sees a release-tagged commit land on release.mainbranch.
  2. erun release reads <projectroot>/<tenant>-devops/VERSION, syncs the chart version / appVersion, creates the release commit and a local tag, pushes the tag and advances the next patch on release.developbranch. erun build --release is the separate act that stamps and tags that same version, builds the multi-arch images, runs push at it (per-arch tags + manifest list + the runtime chart), verifies each published manifest resolves, and only then reports the version it released.
  3. A subsequent erun deploy <env> --version <released version> against a runtime env installs the now-published image and chart from the registry by reference.

release.mainbranch does not have to stand still for the duration of a release. A release re-reads it just before the build and refuses if it moved, and its final push rebases onto a branch that moved while the build was running — so a pull request merging mid-release costs seconds or nothing, never a published version the repository has no commits for. See Release version policy · Lifecycle algorithm.

A release reaches a protected branch under whichever credential is in use, and every push it makes — the --follow-tags push that makes its generated commits public, and the tag push that publishes the version — reports a ruleset bypass the same way erun exec push does: naming the ref and the rules GitHub stepped over, and never failing the push, because the push landed. erun exec reconcile-bypass is what resolves such a push afterwards against the gate run that accounts for it.

For projects that use a single-branch trunk model, set both fields to the same branch.