Reverts the loop-1 prerelease channel that was publishing npm as vX.Y.Z-rc.N to the `rc` dist-tag instead of `latest`. `main` is now a stable semantic-release channel again: every merge publishes vX.Y.Z immediately to npm @latest. The rc.1 soak window that guards Docker mutable tags is moved entirely into the Docker publish workflow: - `.releaserc.cjs`: remove `prerelease: 'rc'` from productionConfig, restore `branches: ['main']` (stable). Restore full successComment and `released` label. Keep loop-1 releaseNotesGenerator additions (revert section, breaking change comment). - `docker-release.yml`: every `release: published` event publishes only the immutable `:<ver>` Docker tag. `promote-mutable-tags` job now gates exclusively on `workflow_dispatch` with `promote_to_latest=true` — no longer triggered automatically by non-prerelease release events. - `promote-release.yml`: rewritten as a dispatch wrapper that validates the stable tag exists and is not a prerelease, verifies the immutable Docker image is in the registry, then dispatches docker-release.yml with `promote_to_latest=true`. Removes the `gh release edit --prerelease=false` approach that required the rc soak to be wired through GitHub release state. - `docs/release-process.md`: updated to reflect the decoupled model — npm @latest is immediate; Docker :latest requires manual promote after soak. Documents the `why` split between npm and Docker soak windows.
4.2 KiB
CCS Release Process
CCS uses a decoupled release model: every merge to main immediately publishes
a stable npm @latest release and an immutable Docker :<ver> tag. Docker
mutable tags (:latest, :<MAJOR>, :<MINOR>) require a separate manual
promote step after an operator-verified soak window. This decouples the npm
ecosystem from the Docker stability gate.
Phase 1 — Automatic stable release (on every merge to main)
- A PR is merged into
mainwith a conventional commit (feat:,fix:, etc.). release.ymltriggers semantic-release, which reads.releaserc.cjs.- Because
mainis a stable channel, semantic-release cuts a GitHub release taggedvX.Y.Zand publishes the npm package to the@latestdist-tag immediately. No rc channel, no soak delay on npm. docker-release.ymltriggers on therelease: publishedevent and:- Validates the tag as stable semver (
vX.Y.Z). - Builds the integrated image for
linux/amd64andlinux/arm64. - Pushes only the immutable
ghcr.io/kaitranntt/ccs:X.Y.Ztag. - Signs the image with cosign (keyless OIDC).
- Runs smoke tests (
smoke-testjob). - Mutable tags (
:latest,:<MAJOR>,:<MINOR>) are not added at this stage —promote-mutable-tagsonly runs on explicitworkflow_dispatchwithpromote_to_latest=true.
- Validates the tag as stable semver (
Phase 2 — Manual promotion to Docker mutable tags (rc.1 soak window)
After the immutable :<ver> Docker image has soaked (typically 24 h with no
reported issues), the operator promotes mutable tags:
-
Verify the immutable image is healthy:
docker pull ghcr.io/kaitranntt/ccs:X.Y.Z docker run --rm -p 3000:3000 -p 8317:8317 ghcr.io/kaitranntt/ccs:X.Y.Z # check http://localhost:3000 and http://localhost:8317 -
Optionally verify the cosign signature:
cosign verify \ --certificate-identity-regexp "https://github.com/kaitranntt/ccs/.github/workflows/docker-release.yml" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/kaitranntt/ccs:X.Y.Z -
Run the
promote-releaseworkflow via GitHub Actions UI or CLI:gh workflow run promote-release.yml \ --field tag=vX.Y.ZThis dispatches
docker-release.ymlwithpromote_to_latest=true, which triggers thepromote-mutable-tagsjob to add:latest,:<MAJOR>, and:<MINOR>viadocker buildx imagetools create.Alternatively, dispatch
docker-release.ymldirectly:gh workflow run "Publish Docker Image" \ --field tag=vX.Y.Z \ --field promote_to_latest=true
Why npm and Docker have different soak windows
- npm
@latest: Published immediately on everymainmerge. npm users who pin a version are unaffected; users who runnpm install -g @kaitranntt/ccsget the latest immediately. Rollback isnpm install -g @kaitranntt/ccs@X.Y.Z. - Docker
:latest: Promoted only after operator confirmation. Users who pull:latestor rundocker pullwithout a pinned tag are shielded from a bad image. The immutable:<ver>tag is always available for pinned usage from the moment of release.
Verifying the promotion
# Confirm :latest points to the promoted digest
docker buildx imagetools inspect ghcr.io/kaitranntt/ccs:latest
# Confirm npm @latest updated (happens automatically at Phase 1)
npm view @kaitranntt/ccs dist-tags
Rollback
If a promoted release is found to be bad:
# Repoint :latest to the previous known-good immutable tag
docker buildx imagetools create \
--tag ghcr.io/kaitranntt/ccs:latest \
ghcr.io/kaitranntt/ccs:PREVIOUS.VERSION
# For npm, publish a fix as a new patch release (do not unpublish)
# Unpublishing npm packages causes downstream breakage for pinned consumers.
Branch / tag taxonomy
| Branch | Semantic-release channel | npm dist-tag | Docker tag (on release event) | Docker mutable (on promote) |
|---|---|---|---|---|
main |
stable | @latest |
:<ver> (immutable, immediate) |
:latest, :<MAJOR>, :<MINOR> (after soak) |
dev |
dev prerelease |
@dev |
not published | not published |