Releases¶
The contract iiiris maintains for releases: what triggers a
release, what artifacts ship, what versioning rules apply, and
how latest, rollback, and release notes are handled. Companion
to the operator-facing walkthroughs in
../deployment.md — this spec
captures what the release process guarantees; that file
captures how an operator consumes a release.
What a release is¶
A release is a signed annotated tag matching
v\d+\.\d+\.\d+ pushed to the GitLab origin. Pushing such a tag
fires the release stage in .gitlab-ci.yml, which produces
the artifact set below and a GitLab Release entry. Nothing else
is a release:
- Branch pushes are not releases.
- Manual pipeline reruns on a tag are not new releases (they may republish the same artifacts; the tag identifies the release, not the pipeline run).
- A push of an unsigned tag is rejected (see Signing below).
The tag is authoritative. The pipeline that produced its artifacts is just a build of that tag.
Signing¶
Release tags must be signed (git tag -s vX.Y.Z). Signing is
load-bearing because the rollback contract is forward-only —
published tags and images are never deleted (see
Rollback). A signed tag makes any retroactive
rewrite of a release identifier detectable, which is the only
defence against an attacker (or a confused maintainer) silently
swapping out what vX.Y.Z points at.
The scripts/cut-release.sh helper enforces git tag -s; the
spec requires it.
Versioning¶
Strict Semantic Versioning, currently
pre-1.0 (0.X.Y).
Pre-1.0 rules¶
While the major version is 0, a minor bump (0.X.0) is
required for any breaking change to:
- The IIIF API surface. info.json field names or shapes,
the set of supported formats/qualities/extra-features, status
code mapping, the route grammar in
iiif.v{2,3}.ParsePath. - The YAML config schema or
IIIRIS_*env vars. Renamed or removed fields, changed defaults that affect existing deployments, new required fields without aconfig.Default()value. - CLI flags / binary invocation. Renamed or removed flags.
The surface is currently minimal (
-config); the rule applies to anything added later from the time it ships.
A patch bump (0.X.Y → 0.X.Y+1) covers everything else:
bug fixes, internal refactors, dependency updates, performance
work, log-format tweaks, and cache-format changes (which are
self-healing — see below).
Cache format is not a release surface¶
The filesystem cache backend's on-disk format is governed by
cacheKeyVersion in internal/auth/cachekey.go (see
cache-filesystem.md and
scaffold.md). When the version
bumps, iiirisd detects the legacy state on attach and wipes it
automatically; operator-dropped files are preserved. From the
operator's perspective the upgrade is seamless — caches
re-warm, configuration doesn't change. So cache-format changes
are patch bumps, not minor.
When 1.0 happens¶
1.0 graduates the surfaces above into long-term compatibility
guarantees: a major bump (1.X → 2.X) is required for any
breaking change to the IIIF API surface, config schema/env
vars, or CLI flags. 1.0 itself is declared in its own brief
when:
- The IIIF Image API surface is stable against both 2.x and 3.0 validators (it is today) and no near-term work is expected to alter it.
- The YAML config schema has stabilised — no field churn over several minor releases.
- The CLI flag surface has had a deliberate design pass (more
than just
-config).
Until then, the pre-1.0 rules apply and operators understand minor bumps may require attention.
Cadence¶
Ad-hoc. Releases are cut when meaningful work has shipped;
there is no fixed schedule. Pre-release channels (-rc,
-beta, nightly images) are not in scope — only stable
vX.Y.Z tags produce artifacts.
Artifact set¶
Each release publishes:
| Artifact | Location | Notes |
|---|---|---|
| Distroless container, multi-arch | ${CI_REGISTRY_IMAGE}:vX.Y.Z + :latest |
linux/amd64 + linux/arm64. Default for docker pull. |
| Debian-slim container, multi-arch | ${CI_REGISTRY_IMAGE}:vX.Y.Z-debian |
No :latest-debian. Fallback for shell-in-container debugging. |
| Linux/amd64 binary | Generic package registry, linked from the Release | Stripped, version-stamped. Requires libvips on host. |
| Linux/amd64 binary SHA256 | Generic package registry, linked from the Release | .sha256 file alongside the binary. |
| Windows/amd64 zip | Generic package registry, linked from the Release | iiirisd.exe + bundled MSYS2 UCRT64 DLLs + compliance bundle. Ships when the Windows pipeline runs (see Windows artifact gating below). |
| Windows/amd64 MSI | Generic package registry, linked from the Release | WiX v5 installer. Ships under the same gating as the zip. |
| Windows artifact SHA256s | Generic package registry, linked from the Release | .sha256 files for both the zip and MSI. |
| GitLab Release entry | GitLab Releases UI | Description sourced from CHANGELOG.md; tag annotation as one-line supplemental summary; asset links to the package-registry binaries + container images. |
The CI registry is the sole publication target. Mirroring to Docker Hub, GHCR, or quay.io is not in scope.
Downloadable binaries are public. Release binaries are uploaded to the
project's generic package registry (packages/generic/iiiris/<tag>/…),
not attached as job artifacts. Generic packages on a public project are
anonymously downloadable independent of builds_access_level, so release
downloads are public even while CI logs/artifacts stay members-only. The
Release is created — and its asset links attached — via the Releases API in
scripts/gitlab-release.sh, not the
GitLab release: keyword (which the runner executes via release-cli, absent
from our images and runner-version dependent — the cause of v0.5.0's
release-publish failure).
latest semantics¶
:latest always points at the newest stable vX.Y.Z. The
release pipeline re-tags :latest on every release; operators
on :latest follow the project's stable line. There is no
"pinned 0.x" alternative — pre-1.0 minor bumps may require
operator attention, which is the cost of pre-1.0.
Windows artifact gating¶
build-windows-amd64, build-windows-msi, and the
release-binary-windows-* re-export jobs are gated by
RUN_WINDOWS=1 because the EC2-orchestrator path that should
drive them on every tag push hasn't been driven end-to-end
yet. Tracked in
../briefs/WINDOWS_EC2_RUNNER.md.
Until that brief lands:
- Releases that include Windows assets are triggered with the
RUN_WINDOWS=1CI variable set on the tag pipeline. - Releases without
RUN_WINDOWS=1ship Linux + container artifacts only.release-publishdeclares its Windows-job dependencies withneeds: optional: trueso it remains valid when those jobs aren't in the pipeline.gitlab-release.shuploads + links a Windows asset only when its file is present, so a non-Windows release has no dangling Windows links (the earlier 404-stub wart is gone).
This is the only documented variability in the artifact set; removing it is the resolution of the EC2 runner brief.
Release notes¶
The GitLab Release description is sourced from CHANGELOG.md:
the body of the ## [vX.Y.Z] - YYYY-MM-DD section is
extracted (heading line stripped) by
scripts/gitlab-release.sh
and posted as the Release description. The tag annotation
contributes a one-line supplemental summary rendered above
the CHANGELOG content.
This makes CHANGELOG.md the single source of truth for
release content. Tag annotations are a hook, not the body.
Rollback¶
Forward-only. A bad release is fixed by cutting a new patch
release with the revert (or fix). :latest re-points
forward on the next release.
Published tags and container images are never deleted. Deployments that pinned a bad version need a clean upgrade path, not a 404. The operational impact of a registry delete (every deployment pulling the bad tag fails) is worse than the impact of the bad release itself in nearly all scenarios.
If a release is severe enough that it must not be used at all, the appropriate response is:
- Cut the patch release with the revert. Document the
issue in
CHANGELOG.mdunder the new version. - Communicate (whatever channels apply) that operators should upgrade to the patch.
- Optionally, add a note to the bad release's GitLab Release entry pointing at the patch.
How to cut a release¶
The maintainer flow, end to end:
- Confirm
[Unreleased]inCHANGELOG.mdaccurately reflects what's shipped since the last release. Edits to[Unreleased]go in normal feature/fix MRs, not at release time. - Pick a version per Versioning.
- Run
scripts/cut-release.sh vX.Y.Z. The script: - Validates the version format, working tree, and current branch.
- Rewrites
CHANGELOG.md: renames[Unreleased]to[vX.Y.Z] - YYYY-MM-DD; inserts an empty[Unreleased]section above. - Commits on
release/vX.Y.Z, pushes, opens an MR via theglab-mrflow. - Review and merge the release MR.
- Tag the merge commit with a signed annotated tag:
The one-line message is what appears as the supplemental
summary on the GitLab Release; the body comes from
CHANGELOG.md. - CI runs the release pipeline. Verify the GitLab Release
entry, asset links, and
:latesttag in the registry. - If Windows assets are part of the release, the tag
pipeline must be triggered with
RUN_WINDOWS=1set — see Windows artifact gating.
Out of scope (deliberately)¶
- Image signing (cosign), SBOM generation, provenance attestations. Supply-chain hygiene worth doing, but adds key-management and tooling surface that's better layered in a follow-up brief once this baseline is stable.
- Mirroring to Docker Hub / GHCR / quay.io. GitLab Container Registry is the sole publication target.
- Pre-release channels (
-rc,-beta, nightlies). Only stablevX.Y.Ztags produce artifacts. - Auto-tagging from CI. The human runs
cut-release.sh, reviews the MR, merges, and tags. Pushing a tag is the explicit "ship this" gesture; CI does not infer it. - Automated Windows artifact builds on every release.
Tracked in
../briefs/WINDOWS_EC2_RUNNER.md; resolution of that brief retires theRUN_WINDOWS=1variability described above. - Multi-registry mirroring or operator-facing release metadata feeds (RSS, JSON manifest). Operators consume the GitLab Releases UI directly.