ADR 0021: Versioning and releases: GitVersion, GHCR images, a pre-release first
- Status: Accepted
- Date: 2026-10-07
- Builds on: ADR 0018, the release setup of ariadne and
GravionLabs/ci
Context
Elysion has no version (0.0.0 in every package.json), no tag, no release and no published image: to run it somebody has to build four images from a checkout. The owner wants a first pre-release whose version comes from the git history with GitVersion, in the way ariadne does it (images on GHCR, a GitHub release, release notes from the Conventional Commits, a documentation site on GitHub Pages).
ariadne uses the shared GravionLabs/ci repository: the action versioning/determine-version (GitVersion), the reusable workflow docker-package.yml (one image, one release) and the action versioning/gh-release (tag, release, git-cliff notes). Elysion has four images, and the reusable workflow makes one release per call, so it does not fit as it is. It also gives every build on main the tags latest, <major> and <major>.<minor>.<patch>, which would put a pre-release on latest.
Decision
- GitVersion computes the version from the history (
GitVersion.yml,ContinuousDelivery,next-version: 0.1.0). Onmainit has the labelbeta, so the version is0.1.0-beta.<commits since the last tag>(0.1.0-beta.126for the first release).+semver: minoror+semver: majorin a commit message bumps that part. A stable release is a change toGitVersion.yml: delete thelabelline and setnext-versionto1.0.0; from then on a merge tomainis1.0.0,1.0.1, and so on. - Every merge to
mainthat passes the checks is a release (releaseinci.yml, afterverify,dotnet-coverageande2e): the workflow works out the version once, builds and pushes the four images in parallel (a matrix), and then creates the tag and the GitHub release once withversioning/gh-release(git-cliff notes fromcliff.toml, marked as a pre-release by the version's label). Pushes tomainare not cancelled by a newer push, so a release always runs to its end. - The images are
ghcr.io/gravionlabs/elysion-{frontend,bff,realtime,business-backend}, forlinux/amd64andlinux/arm64. A pre-release is tagged<version>,<version>-<commit>and the moving tagnext; only a stable release getslatest,<major>and<major>.<minor>. This logic is in the workflow and not in the shared reusable workflow, whose tags are not right for a pre-release. - Release notes come from the Conventional Commits of the squash-merged pull requests (the squash body's
* type: messagelines are read as commits of their own).chore,ci,style,testandbuildare left out. The notes of a release are what came after the last tag of any kind, pre-releases included. Pull request titles are therefore Conventional Commits (feat: ...,fix: ...,docs: ...). - A pull request that touches the images or the demo (
container.yml) builds the four images and starts the stack, andscripts/demo-smoke.shchecks that it works: the app is served,dev,dev1anddev2log in, the API answers, a board is made and a realtime token issued. The images are not pushed from a pull request. - Self-hosting is described in docs/self-hosting.md:
docker-compose.ymlruns the published images (ELYSION_VERSION; ADR 0023), and the page lists what a real deployment needs. The demo is not a production setup.
Consequences
- The first merge to
mainafter this change createsv0.1.0-beta.N, N being the number of commits onmain, and the first release notes list the whole history. After that N counts up from the last tag. - The shared actions are pinned to a commit of
GravionLabs/ci(the one ariadne uses). The repository must be allowed to use them, which depends on thecirepository's Actions access settings; the first run shows it. The workflow also needspackages: writefor GHCR, and the published packages are private until their visibility is set to public (package settings on GitHub). - Building
linux/arm64under QEMU makes the release slower (the .NET image most of all); the build cache (type=gha, one scope per image) keeps repeat builds short. - Every pull request is tested as an upgrade from the last pre-release (
nexton GHCR): theupgradejob ofcontainer.ymlfills that stack with data (scripts/fill-stack.mjs), takes a backup, starts the pull request's images on the same volumes and checks that the data is all still there (#694). A change that needs a manual step carries!in its Conventional Commit and comes first in the release notes ("Breaking changes"). - The images run as a non-root user (nginx-unprivileged 101,
node1000,app1654) since #691; the chart adds a read-only root file system and drops all capabilities. - The Helm chart's default image names are not the GHCR ones yet; the self-hosting page says how to set them.
- The version is not shown in the application yet; the images carry it as OCI labels (
org.opencontainers.image.version). - The documentation site on GitHub Pages is ADR 0022.