Releases & versioning
A release is a vX.Y.Z-tagged image publish. Rather than every app owner
tagging by hand and drifting, MajNet gives one consistent scheme across every
app: the bot cuts the tag, CI builds the image, and you promote it to
production — all through git.
Cut a release
Section titled “Cut a release”From an app’s Releases section, Cut release picks the next version for you. The bot reads the last release and computes the next semver — you choose the bump:
- Patch / Minor / Major — explicit (the menu shows the exact next version).
- Auto — derived from the conventional-commit messages since the last
release. By default
feat:→ minor,fix:→ patch, and a!orBREAKING CHANGE→ major; every other type (chore,docs,refactor, …) is ignored, so it neither forces a patch nor clutters the changelog. The strongest signal across the commits wins. If nothing releasable landed since the last release, there’s simply no candidate to cut.
The bot creates the vX.Y.Z tag on the app repo’s main (through git, App-
signed) — no local tagging, no inconsistency between repos.
The bump map is configurable per app (release.bumps in the ops repo’s
project.yaml): map any commit type to major / minor / patch — e.g. add
perf: patch — while a breaking change always wins as major.
Build → record → promote
Section titled “Build → record → promote”- CI builds it. The tag triggers the managed release workflow, which builds
and pushes the image tagged
vX.Y.Z, pinned by digest. - The bot records it. The registry publish is picked up and the release
appears in the app’s Releases list.
stableauto-tracks the newest tag. - Promote to production. Promote pins the chosen version’s digest into
the project’s ops repo (
production.yaml) via a render PR — merging it deploys. Production only ever runs a version you explicitly promoted.
Monorepo apps: per-app releases
Section titled “Monorepo apps: per-app releases”A monorepo — one repository hosting several apps — can
release each app on its own version and cadence instead of one repo-wide
vX.Y.Z covering everything. Opt an app in by giving it a release: block in
the ops repo’s project.yaml (written for you from the dashboard’s release
settings — no hand-editing, no PR):
apps: - name: sideline-server repo: sideline release: autorelease: auto # off | patch | auto paths: [applications/server/**]With a scope set, cutting that app creates a scoped git tag
@<scope>/<leaf>@vX.Y.Z (the Changesets convention), and each app tracks its own
last version. The image tag stays the plain version, so nothing downstream
changes — only the git tag is scoped. Without a release: block an app keeps the
repo-wide vX.Y.Z scheme, so this is fully backward compatible.
Two extra behaviours make per-app releases first-class:
- Path-scoped changelogs. With
paths, an app’s changelog andautobump consider only the commits that touched those paths — a busy monorepo never inflates one app’s changelog with a sibling’s changes. - The bump lands in the repo. A release commits the bumped
<dir>/package.jsonversion and a prepended<dir>/CHANGELOG.mdentry straight tomain(one commit, no PR), then tags it — so the repo’s own version files stay in sync with the release.
Autorelease
Section titled “Autorelease”An app with autorelease: patch or autorelease: auto cuts itself
automatically on every push to main that touches its paths: patch always
bumps the patch level, auto derives the bump from the conventional commits. It
reuses the same tag → CI path as a manual cut, and release commits are skipped so
a release never re-triggers itself. Manual Cut release still works alongside
it.
Releasing several apps at once
Section titled “Releasing several apps at once”The fleet-wide Releases page (View all → from the top-bar Releases popover) lists every app with an unreleased change as a candidate. Expand a row to review its computed version + changelog and release it inline, or tick several and Release N selected to release them in one action.
A bulk release is batched per repository: all of a monorepo’s selected apps
land in one version-bump-and-changelog commit with one tag each, so sibling
releases never race each other for the same main — the whole repo releases
atomically.
Watching a release land
Section titled “Watching a release land”A release isn’t instant — after you cut it, the version is committed and tagged,
CI builds the image, the publish is recorded, and stable starts tracking it.
The Releases page shows each in-flight release as a live stepper so you can
watch it move through those stages instead of guessing:
committing → tagging → building → published → tracked
Each stage lights up as it completes, and if one fails — a rejected tag, a stuck CI build — it’s marked there with the reason, so you can see exactly where a release stalled. This is especially handy for a bulk release, where several apps advance at once.
Seeing what’s running
Section titled “Seeing what’s running”Every app serves a standard /info endpoint with its build metadata; the
reconciler scrapes it at deploy time, so the dashboard, deploy events, and
render PRs all show the version running per environment — not an opaque
digest. Images are always pinned by digest under the hood; the version is the
human-facing handle.