Skip to content

Versions and tooling ​

Verified against primary documentation on 2026-10-16.

The v prefix ​

The Semantic Versioning FAQ answers "Is v1.2.3 a semantic version?" with "No, 'v1.2.3' is not a semantic version." It adds that prefixing a version with a "v" is "a common way (in English) to indicate it is a version number" (Semantic Versioning 2.0.0). The SemVer grammar has no place for the prefix, so a tag that must be SemVer cannot carry it.

The OCI distribution spec allows it anyway. A tag must match [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127} and be at most 128 characters (OCI Distribution Specification). The grammar admits v1.4.0 and 1.4.0 equally, so it does not decide the question. It also excludes +, which is why Helm rewrites it (see below).

Go modules are the reason git tags keep the prefix: "Each version starts with the letter v, followed by a semantic version", and a non-canonical version such as master is an error outside the main module's go.mod (Go Modules Reference). The go command reads versions from VCS tags, so a Go module published from git needs v1.4.0 there.

What common projects do, read from the registries on 2026-10-16 with crane ls and the Docker Hub API:

ArtifactTag formv
docker.io/library/postgres14.24, 16.15no
docker.io/library/nginx1.31.7, 1.30.6no
docker.io/prom/prometheusv3.13.4, v3.15.0yes
quay.io/prometheus/prometheusv3.9.1, v3-busyboxyes
registry.k8s.io/kube-apiserverv1.9.9, v1.9.9-beta.0yes
registry.k8s.io/pause3.9no
ghcr.io/prometheus-community/charts/prometheus29.36.2, 29.37.0no
registry-1.docker.io/bitnamicharts/nginx25.2.5no
ghcr.io/webassembly/wasi/http (Wasm)0.3.0, 0.3.2no

No v-prefixed tag exists in the postgres or nginx repositories, and none in the Prometheus chart repository. The v in Prometheus and Kubernetes images matches their git tags, so those projects publish the git tag name as the image tag. Charts and Wasm components carry no prefix because Helm and the wasm-pkg-tools registry docs require SemVer tags (see below).

The practice is split, and no registry rule favors either side. The ADR choice follows from the constraints that do exist: charts and components cannot take a v, so dropping it everywhere gives one tag form for every kind. A git tag may keep v1.4.0 for Go and for habit, and the build strips it.

Helm and OCI ​

Helm's chart documentation says a chart version "should follow the SemVer 2 standard but it is not strictly enforced", while "non-SemVer names are explicitly disallowed by the system" (Charts). Versions such as 1.2 are coerced into valid SemVer. The ADR wording that charts require SemVer holds, with the caveat that Helm's own text calls full SemVer 2 "not strictly enforced".

On OCI, "the registry reference basename is inferred from the chart's name, and the tag is inferred from the chart's semantic version". The reference given to helm push "must not contain the basename or tag", so helm push api-1.4.0.tgz oci://<registry>/charts/shop pushes to <registry>/charts/shop/api:1.4.0 (Use OCI-based registries).

Plus signs in versions are rewritten. The Helm source says "OCI artifact references (e.g. tags) do not support the plus sign (+)", so Helm changes "plus (+) to an underscore (_) in chart version tags when pushing to a registry and back to a plus (+) when pulling" (pkg/registry/client.go). The conversion is strings.ReplaceAll(tag, "+", "_") in pkg/registry/reference.go, and the reverse runs when Helm lists tags. Helm's helm.sh/docs pages fetched for this reference did not show the same sentence, so the source code is the primary evidence here. Tools that do not follow the convention break on such tags, which is one more reason the ADR forbids build metadata.

CalVer YYYY.MDD.N ​

calver.org says "unpadded is the sensible default" and that zero-padding "makes the most sense when versions will commonly live in filenames". On SemVer it says "Padding is problematic in a lot of systems, because a padded segment is invalid" under SemVer, which "MUST NOT contain leading zeroes", and "Cargo and Go both enforce strict SemVer, so definitely don't pad there" (Calendar Versioning). It also deprecates the doubled MM and DD spellings in favor of M, D, 0M and 0D.

The SemVer rule applies to numeric identifiers: each of the major, minor and patch parts is 0 or a nonzero digit followed by digits (Semantic Versioning 2.0.0). The ADR's MDD joins the month and a two-digit day into one number:

  • 15 October is 10 and 15, giving 1015, which starts with 1. 2026.1015.0 is valid SemVer.
  • 5 April is 4 and 05, giving 405. The minor part is 405, valid. Padding the month to 0405 makes a leading zero, so 2026.0405.0 is not valid SemVer.
  • 1 January is 101, and 31 December is 1231. No month and day pair joined this way starts with 0, because the month is unpadded.

The ADR's YYYY.MDD.N is a SemVer-safe variant of calver.org's padded YYYY.0M0D forms. The calver.org page does not name MDD as a scheme, so the joined form is the ADR's own convention, built from M and 0D.

docker/metadata-action ​

The type=semver source needs a valid SemVer git tag. Its pattern expressions are (shorthand for .., pre-release included), , , and . The documented example table for the git tag v1.2.3:

PatternResult
v1.2.3
1.2.3
.1.2
vv1

So drops the v and keeps it. Literal text in a pattern is kept, as v shows, so .x yields 1.x. The README has no .x example, so that result is inferred from the template rules and should be run once before relying on it. A pre-release tag extends and only: for v2.0.8-beta.67, yields 2.0.8-beta.67, not 2 (docker/metadata-action).

The type=sha source defaults to format=short and prefix=sha-, giving sha-860c190. The short SHA is 7 characters unless DOCKER_METADATA_SHORT_SHA_LENGTH says otherwise. format=long gives the full commit SHA, and prefix= with an empty value removes the sha- mark.

yaml
- id: meta
  uses: docker/metadata-action@v5
  with:
    images: ghcr.io/${{ github.repository_owner }}/images/shop/api
    tags: |
      type=semver,pattern={{version}}
      type=semver,pattern={{major}}.x
      type=sha,format=long,prefix=

In this ADR a release adds a tag to the image already built for the commit and never rebuilds it, so metadata-action does not push anything here. It only computes the tag names, and the push of the version tag is a registry-side retag. go-containerregistry documents the command as crane tag IMG TAG: "Tag remote image without downloading it", and the tag argument is only the tag, not the full repository (crane tag). The documented equivalence is crane cp registry.example.com/library/ubuntu:v0 registry.example.com/library/ubuntu:v1 and crane tag registry.example.com/library/ubuntu:v0 v1. A release then reads:

sh
crane tag ghcr.io/<owner>/images/shop/api:<commit> "${GITHUB_REF_NAME#v}"

A chart is versioned on its own, so a release takes its version from the version in its Chart.yaml, not from the image's git tag. Only a build that is not a release overrides it:

sh
helm package devops/helm/charts/api
helm package devops/helm/charts/api --version "0.0.0-sha-<commit>" \
  --app-version "<commit>"

wkg and the Wasm component path ​

The wasm-pkg-tools registry metadata document describes one oci block per registry, with a registry host and a namespacePrefix, default empty:

json
{
  "preferredProtocol": "oci",
  "oci": { "registry": "ghcr.io", "namespacePrefix": "webassembly/" }
}

The documented reference is ghcr.io/webassembly/$NAMESPACE/$PACKAGE:$VERSION, for example wasi:http at 0.2.1 becoming ghcr.io/webassembly/wasi/http:0.2.1 (registry metadata). Older configuration spells the keys ociRegistry and ociNamespacePrefix, and they are deprecated. Tags must be valid SemVer, and tooling ignores others when pulling.

The ghcr.io/webassembly/wasi/http repository exists with tags such as 0.3.2, which matches the documented shape.

What this supports for the ADR: registry holds the host alone, as in the ghcr.io example, so the owner or project belongs in namespacePrefix. <owner>/wasm-components/ on GitHub Container Registry, <project>/wasm-components/ on Artifact Registry and wasm-components/ on ECR all yield <registry>/wasm-components/<namespace>/<package>. The prefix is joined directly to the namespace, so the trailing slash in wasm-components/ matters. The metadata document describes the prefix in the registry's discovery file (/.well-known/wasm-pkg/registry.json). This reference did not verify how the wkg client's own configuration file maps a namespace to a registry and whether it accepts the same namespacePrefix key for a registry we run ourselves. Treat the configuration step in the ADR as confirmed for the metadata format and unconfirmed for the exact wkg configuration file syntax, and check it against wkg config before the ADR moves past Draft.

Signatures ​

Signatures, attestations and SBOMs live next to the artifact in the same repository, so a copy that moves only the image manifest leaves them behind. Cosign stores a signature as a tag named from the digest: the registry.k8s.io/pause repository lists tags of the form sha256-<digest>.sig beside its version tags. A tag listing of any signed repository shows them, and a tool that copies one tag does not copy them.

cosign copy is "Copy the supplied container image and signatures", and its documented examples copy the image with its signatures, or only parts with --only=sig,att,sbom (copy.go). The same source marks the command deprecated and points to alternatives: "oras copy -r will copy images and referring artifacts", cosign save and cosign load, or cosign download followed by cosign attach. The ADR requires copying signatures with the artifact but names no tool, and that holds. A deployment guide should name oras copy -r first, whose help describes the recursive flag as copying "the artifact and its referrer artifacts" (oras cp). It is marked preview in the source. Signatures stored as OCI 1.1 referrers and as legacy .sig tags are different storage, so check which one the signer produces before choosing the copy tool.

crane copy is described as "Efficiently copy a remote image from src to dst while retaining the digest value" (crane copy). Its documentation mentions the image only. It makes no claim to copy signatures or attestations, so the ADR statement that a plain pull and push leaves them behind is consistent with it.

Except as otherwise noted, the content of this repository is licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT