0010. Semantic Versioning and Tag-Driven Releases¶
Status¶
Accepted
Context¶
The image has only ever existed as a locally built dev-container:latest.
CHANGELOG.md declares Semantic Versioning and Keep a Changelog, but every
change has accumulated under [Unreleased]: no git tag, no released version,
no published artifact. Consuming projects (see ADR 0006) reference
dev-container:latest and rebuild by hand, so "which image am I running?" has
no stable answer, and there is no way to roll back to a known-good image.
SemVer is defined for APIs. For a tool-aggregate container image the "API" has to be defined explicitly, otherwise every version bump becomes a judgment call with no shared rule.
Decision¶
Adopt Semantic Versioning with annotated git tags vX.Y.Z, starting at
v1.0.0. The version describes the container contract, defined as:
- the set of tools on PATH and their major behaviors;
- the entrypoint interface (
entrypoint.sh setup|start), mount points, named volumes, environment variables, and ports documented in README.md; - the sudo allowlist and the egress/firewall model.
Bump rules:
- MAJOR -- the contract breaks: a tool is removed or renamed, a mount path / volume / env var / port changes meaning, the entrypoint interface changes, or a base change requires consumer action.
- MINOR -- additive: new tools, new services, new features. Also major version bumps of bundled tools (e.g. Terraform 1.x -> 2.x): visible to users, but the container contract itself is unchanged.
- PATCH -- fixes and routine Renovate pin bumps of bundled tools.
Release mechanics (the checklist lives in AGENTS.md "Versioning and Releases"):
- Releases are manual and changelog-gated: the maintainer moves
[Unreleased]to a dated## [X.Y.Z]section, lands it via PR, then pushes the tag. CI refuses a stable tag without a matching changelog section and publishes the section as the GitHub Release notes. - Docker tags on
docker.io/felipecoelho90/dev-container:X.Y.Zis immutable;X.Y,X, andlatestmove with releases. Prereleases (vX.Y.Z-rc.N) push only their exact tag and a GitHub prerelease. - Fix forward, never retag: a bad release is followed by a new patch release, an existing version tag is never overwritten.
- Consuming projects pin the major (
felipecoelho90/dev-container:1) for stability with automatic minor/patch updates; pinX.Y.Zfor full reproducibility.
Consequences¶
Easier:
- Consumers get stable, rollback-able references instead of a moving
latest; "which image?" is answerable. - The changelog gate forces release notes to exist before a release does.
- Renovate's stream of tool bumps maps cleanly to patch releases.
Harder:
- The maintainer must judge bumps against the contract definition and curate the changelog before tagging -- releases are deliberate, not automatic.
latestnow means "latest release", not "whatever was built last"; local development keeps using the locally builtdev-container:latest, which is a different thing by design.