Brings GoReleaser's declarative release workflow to Rust with native Cargo workspace awareness. Exposes build orchestration, cross-compilation via cargo-zigbuild or cross, deterministic artifact generation, and multi-platform packaging (deb, rpm, snap, DMG, MSI). Handles the full pipeline: bump Cargo.toml and Cargo.lock atomically, build for multiple targets, generate checksums and SBOMs, sign with cosign or GPG, publish to crates.io with dependency-ordered polling, push GitHub releases, and announce to Discord or Slack. The tag and bump commands rewrite version files and commit in one shot, avoiding the usual lockfile drift. If you ship Rust binaries and want reproducible releases without stitching together a dozen CI jobs, this wires the whole stack together from a single YAML config.
The release pipeline built for Rust — workspace-aware, reproducible, and signed by default.
Anodizer reads a declarative config file and runs your entire release from a single anodizer release command: build, archive, checksum, changelog, sign, release, publish, and announce. It's built around the Rust ecosystem — Cargo workspaces, Cargo.lock-aware version bumps, crates.io, and byte-reproducible artifacts.
Written by Claude; maintained by us.
See What works (with proof) for a per-feature status — every "live" claim links to a real published artifact you can verify yourself.
Your release is a Cargo workspace — not a bag of loose binaries. anodizer is built that way from the ground up.
anodizer tag and anodizer bump rewrite Cargo.toml and Cargo.lock, then commit, tag, and push atomically — no orphaned bump commit, no hand-rolled git push, no lockfile drift.cargo-zigbuild or cross. No rustup target add rituals, no per-target CI shards to babysit.anodizer check determinism rebuilds them and byte-compares. "Reproducible" becomes a fact your CI enforces, not a claim in your release notes.Then the long tail that Rust authors actually hit: generated per-crate READMEs, cargo-binstall metadata derived straight from your config (no hand-maintained pkg-url that 404s), version_files to pin your docs and install scripts to the released version, and post-release install smoke tests that catch a broken artifact before your users do.
Already know GoReleaser? anodizer's {{ .Field }} template syntax will feel right at home. Moving from cargo-dist, release-plz, or cargo-release? The migration guides map your setup straight over.
Build
cargo-zigbuild, cross, or native cargo buildPackage
Sign
Publish
docker buildxAnnounce
Advanced
{{ .Field }} syntaxmod_timestamp and builds_infoversion_files) to keep docs, scripts, and manifests in lockstep at tagbrew install tj-smith47/tap/anodizer
cargo install anodizer
git clone https://github.com/tj-smith47/anodizer.git
cd anodizer
cargo install --path crates/cli
# Generate a starter config from your Cargo workspace
anodizer init > .anodizer.yaml
# Validate your config
anodizer check
# Check that required tools are available
anodizer healthcheck
# Build a snapshot (no publishing)
anodizer release --snapshot
# Dry run (full pipeline, no side effects)
anodizer release --dry-run
# Auto-tag from commit directives
# (Conventional Commits: feat: → minor, fix: → patch, BREAKING CHANGE: → major)
anodizer tag --dry-run # preview what tag would be created
anodizer tag # create + push the tag, which triggers the release workflow
# Or force a specific tag value:
anodizer tag --custom-tag v0.1.0
For CI-based releases, set GITHUB_TOKEN (or ANODIZER_GITHUB_TOKEN) as a secret — the release pipeline picks it up automatically.
Anodizer uses .anodizer.yaml (or .anodizer.toml) in your project root. Add a schema comment for editor autocomplete:
# yaml-language-server: $schema=https://tj-smith47.github.io/anodizer/schema.json
project_name: myapp
defaults:
targets:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- x86_64-apple-darwin
- aarch64-apple-darwin
- x86_64-pc-windows-msvc
cross: auto
crates:
- name: myapp
path: "."
tag_template: "v{{ Version }}"
builds:
- binary: myapp
archives:
- name_template: "{{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }}"
files: [LICENSE, README.md]
release:
github:
owner: myorg
name: myapp
publish:
cargo: {}
homebrew:
repository:
owner: myorg
name: homebrew-tap
See the full configuration reference and the template reference for all available fields, variables, and filters.
cfgd — declarative, GitOps-style machine configuration management — is anodizer's first real-world adopter and dogfoods every shipped publisher. It's a 4-crate workspace (shared lib + CLI + Kubernetes operator + CSI driver) that ships to crates.io (dependency-aware ordering), GitHub Releases, Homebrew, Scoop, Chocolatey, Winget, the Snap Store, Krew, GHCR, and via cargo binstall — all from one .anodizer.yaml and one tag push.
A condensed slice of cfgd's .anodizer.yaml:
workspaces:
- name: cfgd-core
crates:
- name: cfgd-core
tag_template: "core-v{{ Version }}"
version_sync: { enabled: true, mode: cargo }
- name: cfgd
crates:
- name: cfgd
depends_on: [cfgd-core]
version_sync: { enabled: true, mode: cargo }
universal_binaries:
- name_template: "{{ ProjectName }}"
replace: false
binstall:
enabled: true # pkg-url + per-target overrides derived from archive.name_template
# ... cfgd-operator, cfgd-csi
Every cell of What works (with proof) links to a real published cfgd artifact for the feature in question — that's the verification surface.
Anodizer ships a first-party action, tj-smith47/anodizer-action, which is what this repo dogfoods in its own release.yml:
name: Release
on:
push:
tags: ["v*"]
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- name: Release
uses: tj-smith47/anodizer-action@v1
with:
version: latest # accepts `latest`, `nightly`, or an exact tag (e.g. `v0.5.0`). Pin in production.
auto-install: true # auto-detect nfpm/makeself/snapcraft/cosign/etc from .anodizer.yaml
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
For split/merge fan-out, GPG key import, registry login, and per-platform variants, see anodizer-action and the live .github/workflows/release.yml in this repo.
anodizer release Full release pipeline (--snapshot, --dry-run, --split/--merge, --publish-only, --rollback-only)
anodizer tag Auto-tag from commit directives
anodizer tag rollback Delete anodize-managed tags at a SHA and revert the bump commit
anodizer check Validate configuration + run determinism harness
anodizer preflight Verify the environment can run the configured release (tools, secrets, key material)
anodizer init Generate starter .anodizer.yaml
anodizer healthcheck Probe external tools (nfpm, cosign, ...)
Failure handling is in-process: a failed anodizer release executes the
release.on_failure policy itself (rollback by default — delete the tag,
revert the bump — auto-degrading to hold once a one-way-door publisher like
crates.io has landed), so release workflows need no if: failure() recovery
steps. anodizer tag rollback "$GITHUB_SHA" is the manual recovery command
for killed or held runs. See
Release resilience
for the flag matrix and recovery flows.
Full reference: anodizer --help or the docs site.
Full documentation is available at tj-smith47.github.io/anodizer.
Operator guides:
anodizer check determinism harness, runtime allow-listMIT