Skip to main content

Promote

Move an already-published release from a pre-release track to stable, without rebuilding

anodizer promote moves an artifact you already published from a pre-release track to a stable track — without rebuilding. It re-points the registry's own pointer: a snapcraft channel, an npm dist-tag, an OCI floating tag, or a GitHub release's prerelease flag. The bytes never change; only which track resolves to them does.

This is the "release candidate" workflow: publish 1.4.0 to a candidate track, let it soak, then promote the exact reviewed revision to stable once you trust it.

# Publish a candidate (in your release config / pipeline), then later:
$ anodizer promote --to stable
 promoted snapcraft anodizer rev 42 candidate→stable
 re-tagged npm anodizer@1.4.0 next→latest
 re-pointed docker ghcr.io/acme/app:edge → ghcr.io/acme/app:latest
 flipped github release v1.4.0 prerelease→stable

No config block — and why

Promotion is CLI-driven only. There is deliberately no promote: config field. A static promote: {from: candidate, to: stable} would run on every release and auto-promote the revision you just uploaded — defeating the entire point of a candidate gate. Promotion is a decision a human (or a gated CI job) makes after a soak, so it lives on the command line where that intent is explicit.

anodizer reads your existing publisher config (snapcrafts:, npms:, dockers_v2:, release.github) only to learn each publisher's native track vocabulary and locate its repos — never to trigger a promotion.

Track vocabulary

Pass a canonical track (stable, prerelease, candidate, beta, edge) and each publisher maps it to its own native track. A publisher-native name (e.g. an npm dist-tag you invented) passes through verbatim.

Canonical --tosnapcraft channelnpm dist-tagdocker tagGitHub release
stablestablelatestlatestclear prerelease + make latest
candidatecandidateyour pre-tag¹edgeset prerelease
betabetayour pre-tag¹edgeset prerelease
edgeedgeyour pre-tag¹edgeset prerelease
prereleasecandidateyour pre-tag¹edgeset prerelease

¹ npm's pre-stable dist-tag is your npms[].tag when it names a non-latest tag, otherwise next.

--from (default prerelease) is the source track. It is informational for the publishers that locate the artifact by version or by "newest pre-release"; it selects the source floating tag for docker.

Selecting which artifact to promote

SelectorFlagBehavior
Newest(default)The newest artifact currently on the --from track.
Explicit version--version 1.4.0Promote exactly this version/tag.
Prior run--from-run <id>Promote what a recorded run published (reads dist/run-<id>/report.json). --from-run is the most precise: it moves exactly the revisions that run uploaded, per its recorded evidence.
$ anodizer promote --to stable --version 1.4.0
$ anodizer promote --to stable --from-run 20260712-abc123

Choosing publishers

By default every configured, promotion-capable publisher runs. Narrow with --publishers:

$ anodizer promote --to stable --publishers docker,github

Naming a publisher that does not support promotion is a hard error:

$ anodizer promote --to stable --publishers cargo
error: publisher 'cargo' does not support promotion (promotable: snapcraft, npm, docker, github)

Promotion-capable publishers: snapcraft, npm, docker, github. (cargo, PyPI, and the index publishers publish immutable versions with no mutable track pointer to move.)

What each publisher does

PublisherMechanismRebuild?
snapcraftsnapcraft release <name> <rev> <channel>no
npmnpm dist-tag add <pkg>@<version> <tag> for every platform packageno
dockerdocker buildx imagetools create --tag <repo>:<to> <repo>:<from> (registry-side manifest copy)no
githubPATCH /repos/{owner}/{repo}/releases/{id} flipping prereleaseno

npm re-tags the whole package family — the metapackage and every per-platform package — so a promoted release is consistent across every install target. docker and github operate on every configured image repo / release repo, deduplicated so a lockstep workspace sharing one tag flips it once.

Credentials

Promotion needs the same credentials as the original publish:

  • snapcraft — a logged-in snapcraft (Snap Store credentials).
  • npmNPM_TOKEN (OIDC publish credentials cannot move a dist-tag).
  • dockerdocker buildx authenticated to the registry.
  • githubANODIZER_GITHUB_TOKEN / GITHUB_TOKEN / GH_TOKEN, or --token.

A live promotion preflights every selected publisher's tool and credentials and fails fast before mutating anything — so a missing token stops the run before the first registry is touched, never halfway through.

Dry run

--dry-run resolves the full plan and prints exactly what would happen, running no external command and requiring no credential:

$ anodizer promote --to stable --dry-run
 (dry-run) would promote snapcraft newest candidate→stable
 (dry-run) would promote npm newest next→latest
 (dry-run) would re-point docker ghcr.io/acme/app:edge → ghcr.io/acme/app:latest
 (dry-run) would flip github release newest on acme/app (prerelease→stable)

Run the dry-run first whenever you are unsure which artifact the selector resolves to.