Release and roll back
A release is a tag, an image built from it on the host that serves it, the image id that identifies
it, a smoke test against a host, and a rollback path. This page is written against 1.1.0; every
release edits the tag and the id recorded below.
What a release is
Section titled “What a release is”| Thing | Value |
|---|---|
| Version | 1.1.0 in package.json and the newest CHANGELOG.md heading |
| Git tag | v1.1.0 on the release commit |
| Image | suite:v1.1.0, built on the host that runs it and also tagged suite:local |
| Identity | the image id from docker image inspect --format '{{.Id}}' suite:v1.1.0 |
| Change record | the matching CHANGELOG.md section |
| Evidence | the pnpm verify run and the security scan output, quoted in the release notes |
SUITE_VERSION is baked into the image at build time, so a running container reports what it is:
version in /api/admin/status, suite_build_info{version="…"} 1 in /metrics, and the
server ready line. The policy behind the scheme is ADR-0026 in
the internal decision log.
The lead creates the tag after integration. A workstream does not push one.
Cut one
Section titled “Cut one”package.jsonand the newestCHANGELOG.mdheading both say1.1.0.node scripts/docs-check.mjsandnode scripts/generate-limits.mjs --checkare clean.pnpm verifyis green on the commit being tagged, and a fullpnpm e2epassed on that same commit, in that order, with nothing already listening on:5173or:1235. A tag is the milestone where the full suite runs; the commands are in test-and-verify.md.git tag -a v1.1.0 -m "Suite 1.1.0".git push origin v1.1.0.
If workflows can start, .github/workflows/release.yml runs three jobs:
| Job | Does | Fails when |
|---|---|---|
preflight |
Installs, compares GITHUB_REF_NAME with package.json, runs both docs checks |
The tag and the version disagree, or a doc is stale |
image |
Builds the runtime target with SUITE_VERSION=<tag> and pushes :<tag>, :sha-<12> and :latest to GHCR |
The build fails |
release |
Opens a GitHub release whose body is the changelog section plus the image digest and a rollback link | CHANGELOG.md has no section for the version |
Nothing on this page depends on either workflow file. Every workflow on this account still ends in
startup_failure with zero jobs, which is account state rather than workflow content, so the release
image is built on the host that serves it.
Build the image on the host
Section titled “Build the image on the host”The build argument is the tag, and the two -t names are what the host later rolls back to and runs:
git checkout v1.1.0docker build --target runtime --build-arg SUITE_VERSION=v1.1.0 \ -t suite:v1.1.0 -t suite:local .docker image inspect --format '{{.Id}}' suite:v1.1.0Record the image id in the release notes. A host-built image has no RepoDigests, so the id is the
identity every later rollback names, and docker image ls shows it next to the tag.
deploy/deploy.sh runs the same build with the tag at HEAD, plus the backup, the rollback tag and
the restart.
Do not tag unless the local gate is green on the exact commit. The tag is the only quality claim this project can currently make.
Smoke test the release
Section titled “Smoke test the release”Against the host that will serve it, before announcing anything:
sed -i -E 's|^#?[[:space:]]*SUITE_IMAGE=.*|SUITE_IMAGE=suite:v1.1.0|' .envdocker compose up -d --no-build app # the new tag; Caddy keeps runningdocker compose ps # app healthy, caddy upcurl -fsS https://$SUITE_DOMAIN/health # {"ok":true}# The operator API answers a session, so this runs on the box: SUITE_METRICS_LOCAL=1 lets a# loopback request read /metrics with no credential, and the version stamp lives there.curl -fsS http://127.0.0.1:1235/metrics | grep suite_build_info/api/admin/status must report "version":"v1.1.0" and suite_build_info must carry the same tag,
because SUITE_VERSION reaches the process verbatim. Then, by hand: open a workspace, upload an
attachment, reload, open the same workspace in a second tab, and confirm the published limits still
match what the server reports on a refusal (reference/limits.md). A
container that reports the wrong version is not a release, however healthy /health looks.
Rollback
Section titled “Rollback”Rollback restarts app on the tag taken before the upgrade. It needs no code change and no build, and
on a host build it is the only rollback there is: a build overwrites suite:local, so the pre-upgrade
tag is what names the previous image.
-
Find the tag.
deploy/deploy.sh --listprints everysuite:rollback-*on the host, and the release notes record the version each upgrade started from. -
Point
appat it and recreate that service only:Terminal window deploy/deploy.sh --rollback suite:rollback-v1.1.0# by hand: SUITE_IMAGE=suite:rollback-v1.1.0 docker compose up -d --no-build app -
Confirm
/healthanswers and/api/admin/statusreports the expectedversion. -
If the release changed a stored format (a schema bump, a new store, a backup manifest change), the older binary may refuse the newer data. Stop
app, restore the pre-upgrade backup withdeploy/restore.sh --force(operate.md), then start the older image. An older binary against newer data is not a rollback. -
Record the tag restored, its image id, the time, and whether a restore was needed, in the release notes.
caddy never moves during a rollback. It serves a static bundle from its own image and proxies to
app.
Not in a release
Section titled “Not in a release”A release omits image signing and verification on pull, SBOM generation and attestation,
multi-architecture images, self-hosted runners, and a staging environment or canary. There is one host
and one tag. Adding any of them changes release.yml and should land with an ADR rather than by habit.