Charon follows Semantic Versioning 2.0.0:
- MAJOR.MINOR.PATCH (e.g.,
1.2.3)- MAJOR: Incompatible API changes
- MINOR: New functionality (backward compatible)
- PATCH: Bug fixes (backward compatible)
alpha: Early development, unstablebeta: Feature complete, testing phaserc(release candidate): Final testing before release
Example: 0.1.0-alpha, 1.0.0-beta.1, 2.0.0-rc.2
Charon uses googleapis/release-please-action
to compute versions, maintain a standing release PR, and cut tags +
GitHub Releases from Conventional Commit history. There is no manual
tagging step and no in-repo version manifest to hand-edit — .release-please-manifest.json
is the single source of truth for "what version are we at," kept in
sync by release-please itself.
-
Commits land on
main: every push tomainruns.github/workflows/release-please.yml, which walks Conventional Commits since the last release and opens or updates a standingchore(main): release X.Y.Zpull request. If nothing releasable (feat:/fix:) has landed since the last release, no PR is opened or updated. -
A human merges the release PR: merging it is what actually ships a release — nothing goes out automatically before that.
-
On merge, release-please:
- Tags the merge commit
vX.Y.Z(bare, nocharon-prefix — pinned viainclude-component-in-tag: falseinrelease-please-config.json) - Creates the GitHub Release for that tag
- Tags the merge commit
-
Downstream, independently of release-please:
.github/workflows/orthrus-build.ymltriggers on the newv*tag and publishes semver-tagged Orthrus agent images- The next
nightly-build.ymlrun picks up the new tag viascripts/generate-changelog.sh'sgit tag -l 'v*'scan and regenerates the in-app "What's New" changelog data
-
Container tags are published (via
.github/workflows/docker-build.yml, triggered on the branch push, not the tag):
v1.0.0(exact version)1.0(minor version)1(major version)latest(for non-prerelease on main branch)
Do not manually push v* tags. A manually-created tag desyncs
.release-please-manifest.json (which release-please treats as its
source of truth, not live tag state) from the repo's real tag
history — let release-please create every release tag going forward.
release-please-config.json sets skip-changelog: true, so this
hand-curated CHANGELOG.md is never touched by release-please's PRs
or Release generation.
Release sign-off is blocked until all items below pass in the same validation run.
Enforcement points:
-
Release sign-off checklist/process (mandatory): All gates below remain required for release sign-off.
-
CI-supported checks (current):
.github/workflows/docker-build.ymland.github/workflows/supply-chain-verify.ymlenforce the subset currently implemented in workflows. -
Manual validation required until CI parity: Validate any not-yet-implemented workflow gates via VS Code tasks
Security: Full Supply Chain Audit,Security: Verify SBOM,Security: Generate SLSA Provenance, andSecurity: Sign with Cosign. -
Digest freshness/parity: Capture pre-push and post-push index digests for the target tag in GHCR and Docker Hub, confirm expected freshness, and confirm cross-registry index digest parity.
-
Per-arch parity: Confirm per-platform (
linux/amd64,linux/arm64, and any published platform) digest parity between GHCR and Docker Hub. -
Immutable digest scanning: Run SBOM and vulnerability scans against immutable refs only, using
image@sha256:<index-digest>. -
Artifact freshness: Confirm scan artifacts are generated after the push timestamp and in the same validation run.
-
Evidence block present: Include the mandatory evidence block fields listed below.
- Tag name
- Index digest (
sha256:...) - Per-arch digests (platform -> digest)
- Scan tool versions
- Push timestamp and scan timestamp(s)
- Artifact file names generated in this run
latest: Latest stable release (main branch)nightly: Latest nightly build (nightly branch, rebuilt daily at 02:00 UTC)nightly-YYYY-MM-DD: Date-specific nightly buildnightly-<sha>: Commit-specific nightly builddevelopment: Latest development build (development branch)v1.2.3: Specific version tag1.2: Latest patch for minor version1: Latest minor for major versionmain-<sha>: Commit-specific build from maindevelopment-<sha>: Commit-specific build from development
# Use latest stable release
docker pull ghcr.io/wikid82/charon:latest
# Use specific version
docker pull ghcr.io/wikid82/charon:v1.0.0
# Use latest nightly build (automated daily at 02:00 UTC)
docker pull ghcr.io/wikid82/charon:nightly
# Use date-specific nightly build
docker pull ghcr.io/wikid82/charon:nightly-2026-01-13
# Use commit-specific nightly build
docker pull ghcr.io/wikid82/charon:nightly-abc123
# Use development builds (unstable, every commit)
docker pull ghcr.io/wikid82/charon:development
# Use specific commit from main
docker pull ghcr.io/wikid82/charon:main-abc123Nightly builds provide a testing ground for features before they reach main:
- Automated: Built daily at 02:00 UTC from the
nightlybranch - Source: Auto-merged from
developmentbranch - Purpose: Pre-release testing and validation
- Stability: More stable than
development, less stable thanlatest
When to use nightly:
- Testing new features before stable release
- Validating bug fixes
- Contributing to pre-release testing
- Running in staging environments
When to avoid nightly:
- Production environments (use
latestinstead) - Critical infrastructure
- When maximum stability is required
Charon uses the following version hierarchy:
- Stable releases:
v1.2.3(highest precedence) - Nightly builds:
nightly-YYYY-MM-DDornightly-{sha} - Development builds:
developmentordevelopment-{sha}(lowest precedence)
Nightly builds use multiple tag formats:
nightly: Always points to the latest nightly build (floating tag)nightly-YYYY-MM-DD: Date-specific build (e.g.,nightly-2026-01-13)nightly-{sha}: Commit-specific build (e.g.,nightly-abc1234)
Tag characteristics:
| Tag Format | Immutable | Use Case |
|---|---|---|
nightly |
No | Latest nightly features |
nightly-2026-01-13 |
Yes | Reproducible date-based testing |
nightly-abc1234 |
Yes | Exact commit testing |
Version in API responses:
Nightly builds report their version in the health endpoint:
{
"version": "nightly-2026-01-13",
"git_commit": "abc1234567890def",
"build_date": "2026-01-13T02:00:00Z",
"branch": "nightly"
}curl http://localhost:8080/api/v1/healthResponse includes:
{
"status": "ok",
"service": "charon",
"version": "1.0.0",
"git_commit": "abc1234567890def",
"build_date": "2025-11-17T12:34:56Z"
}View version metadata:
docker inspect ghcr.io/wikid82/charon:latest \
--format='{{json .Config.Labels}}' | jqReturns OCI-compliant labels:
org.opencontainers.image.versionorg.opencontainers.image.createdorg.opencontainers.image.revisionorg.opencontainers.image.source
Local builds default to version=dev:
docker build -t charon:dev .Build with custom version:
docker build \
--build-arg VERSION=1.2.3 \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg VCS_REF=$(git rev-parse HEAD) \
-t charon:1.2.3 .release-please's standing release PR body is generated from commit messages. Use conventional commit format:
feat:New featuresfix:Bug fixesdocs:Documentation changeschore:Maintenance tasksrefactor:Code refactoringtest:Test updatesci:CI/CD changes
Example:
git commit -m "feat: add TLS certificate management"
git commit -m "fix: correct proxy timeout handling"- CI derives the release
Versionfrom the Git tag (e.g.,v1.2.3) and embeds this value into the backend binary via Go ldflags; frontend reads the version from the backend's API. This avoids automatic commits tomain. - release-please creates the tag and the GitHub Release together when its standing release PR is merged — see "Canonical Release Process (release-please)" above.