Praxis uses Semantic Versioning. The workspace
version is the single source of truth, defined in
workspace.package.version in the root Cargo.toml. All
workspace crates inherit this version.
Before tagging a release:
- Lints are clean (
make lint) - All tests pass locally (
make test && make test-integration && make test-conformance) - Dependency audit passes (
make audit) - SemVer compliance verified (
make semver) - Benchmarks have been run; performance is similar or better than the previous release
- Version in root
Cargo.tomlis bumped (bothworkspace.package.versionandworkspace.dependenciesinter-crate versions) -
Cargo.lockis regenerated with the new version -
make publish-dry-runsucceeds (add--allow-dirtywhen running against uncommitted changes) -
SECURITY.mdlists the new minor version - GitHub Release changelog is drafted (see below)
Tags follow the format v<MAJOR>.<MINOR>.<PATCH> (e.g.
v0.1.0) and must match workspace.package.version;
the release workflow rejects mismatched tags. Push the
tag to the repository:
git tag v0.1.0
git push origin v0.1.0The release runs in two phases
(.github/workflows/release.yaml).
Phase 1 runs on the tag push:
- Validate the tag against the workspace version
- Run the full test suite (skipped when the commit is already green on main)
- Verify every release crate packages cleanly (publish dry run)
- Build and publish the container image to GHCR
- Cut a draft pre-release with generated notes
Phase 2 runs when a maintainer publishes the draft:
- Publish every release crate to crates.io, in dependency order
Review and edit the draft notes, then publish the
release from the GitHub UI. Publishing performs the real
crates.io publish using the RUST_CRATES_PUBLISH_TOKEN
secret; nothing reaches crates.io until you publish the
release.
Container images are published to GitHub Container
Registry (GHCR) by the release pipeline. Outside
of a release, the Publish workflow
(.github/workflows/publish.yaml) can be triggered
manually via workflow_dispatch to publish from any
branch or tag.
The publish workflow produces these tags per run:
| Pattern | Example | Description |
|---|---|---|
sha-<hash> |
sha-abc1234 |
Git commit SHA |
<branch> |
main |
Branch name |
<version> |
0.1.0 |
Full semver (from git tag) |
<major>.<minor> |
0.1 |
Major.minor shorthand |
Semver tags are only generated when the workflow runs against a semver git tag.
Praxis uses GitHub Releases for changelogs. Each release is created through the GitHub UI after pushing a tag. Use GitHub's "Generate release notes" feature to auto-populate from merged PRs, then edit for clarity. There is no separate CHANGELOG file.
Release branches are optional and created from tags when
backports are needed. The naming convention is
release/v<MAJOR>.<MINOR>.x (e.g. release/v0.1.x).
Fixes are cherry-picked onto the release branch, a new patch tag is created from it, and the publish workflow is triggered as usual.
The production image is a minimal Alpine container:
- Static musl build with LTO, single codegen unit, and stripped symbols
- Runs as non-root user (
praxis) - Exposes ports
8080(proxy) and9901(admin) - Built-in health check at
http://127.0.0.1:9901/healthy - Config directory:
/etc/praxis
Note: This is subject to change.