Skip to content

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 a config.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.Y0.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.X2.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=1 CI variable set on the tag pipeline.
  • Releases without RUN_WINDOWS=1 ship Linux + container artifacts only. release-publish declares its Windows-job dependencies with needs: optional: true so it remains valid when those jobs aren't in the pipeline. gitlab-release.sh uploads + 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:

  1. Cut the patch release with the revert. Document the issue in CHANGELOG.md under the new version.
  2. Communicate (whatever channels apply) that operators should upgrade to the patch.
  3. 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:

  1. Confirm [Unreleased] in CHANGELOG.md accurately reflects what's shipped since the last release. Edits to [Unreleased] go in normal feature/fix MRs, not at release time.
  2. Pick a version per Versioning.
  3. Run scripts/cut-release.sh vX.Y.Z. The script:
  4. Validates the version format, working tree, and current branch.
  5. Rewrites CHANGELOG.md: renames [Unreleased] to [vX.Y.Z] - YYYY-MM-DD; inserts an empty [Unreleased] section above.
  6. Commits on release/vX.Y.Z, pushes, opens an MR via the glab-mr flow.
  7. Review and merge the release MR.
  8. Tag the merge commit with a signed annotated tag:
    git checkout main && git pull
    git tag -s vX.Y.Z -m "one-line summary"
    git push --tags
    
    The one-line message is what appears as the supplemental summary on the GitLab Release; the body comes from CHANGELOG.md.
  9. CI runs the release pipeline. Verify the GitLab Release entry, asset links, and :latest tag in the registry.
  10. If Windows assets are part of the release, the tag pipeline must be triggered with RUN_WINDOWS=1 set — 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 stable vX.Y.Z tags 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 the RUN_WINDOWS=1 variability described above.
  • Multi-registry mirroring or operator-facing release metadata feeds (RSS, JSON manifest). Operators consume the GitLab Releases UI directly.