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.
Why anodizer?
Your release is a Cargo workspace β not a bag of loose binaries. anodizer is built that way from the ground up.
- It speaks Cargo. Per-crate release cadences, per-crate tags, and a tag resolver let a single crate and a thirty-crate monorepo share one config.
anodizer tag and anodizer bump rewrite Cargo.toml and Cargo.lock, then commit and tag β locally by default, and (on --push) push the bump commit and tag atomically β no orphaned bump commit, no hand-rolled git push, no lockfile drift.
- crates.io, published in the right order. Dependency-aware ordering with sparse-index polling holds each crate until the ones it depends on have propagated β so a workspace publish never races itself into a transient "version not found."
- Cross-compiles without the toolchain tax. musl, glibc, Windows, and macOS from one machine via
cargo-zigbuild or cross. No rustup target add rituals, no per-target CI shards to babysit.
- Reproducible β and it proves it. Deterministic artifacts by default, then
anodizer check determinism rebuilds them and byte-compares. "Reproducible" becomes a fact your CI enforces, not a claim in your release notes.
- Signing and attestation are first-class. cosign + GPG for binaries, archives, checksums, and images, plus SLSA-style build provenance β wired in a few lines, not bolted on after a CVE scare.
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.
Features
Build
- Cross-platform builds via
cargo-zigbuild, cross, or native cargo build
- Per-build hooks (pre/post), environment variables, feature flags, and target overrides
- UPX binary compression with per-target filtering
- Workspace support with per-crate independent release cadences
Package
- Archives in tar.gz, tar.xz, tar.zst, zip, gz, or raw binary format with OS-specific overrides
- Linux packages (.deb, .rpm, .apk, .archlinux, .ipk) via nFPM with full lifecycle scripts
- Snapcraft snaps with prime-dir architecture
- macOS DMG disk images and PKG installers
- Windows MSI and NSIS installers
- Flatpak bundles
- AppImage portable Linux applications
- Makeself self-extracting archives
curl | sh install-script generation β a per-platform asset table derived from your build/archive config (no hand-maintained URLs to 404), with a checksum-verify gate
- Source RPMs (.src.rpm)
- Source archives with file filtering
- SBOM generation (CycloneDX/SPDX)
- Checksums with SHA-256, SHA-512, SHA3, BLAKE2b, BLAKE2s, BLAKE3, CRC32, MD5, and more β combined or split per-artifact sidecars in bare or coreutils (
shasum -c) format
Sign
- GPG and cosign signing for binaries, archives, checksums, Docker images, and SBOMs
- Signed git tags (GPG or SSH) using your existing git signing key
- Multiple independent signing configurations
- Conditional signing via template expressions
- Build provenance attestations (SLSA-style) for binaries and artifacts
Publish
- GitHub/GitLab/Gitea Releases with asset uploads, draft/prerelease detection, header/footer templates
- crates.io with dependency-aware ordering and index polling
- Homebrew formula and cask generation
- Homebrew-core formula bump PRs (bump an existing
homebrew-core formula)
- Scoop manifest generation
- Chocolatey package generation
- Winget manifest generation
- AUR PKGBUILD and .SRCINFO generation
- Krew plugin manifest generation
- Nix derivation generation
- SchemaStore catalog registration for editor autocomplete of your config files
- MCP registry server-manifest publishing (Model Context Protocol)
- Docker multi-arch images via
docker buildx
- Blob storage uploads (S3, GCS, Azure)
- NPM per-platform binary packages and PyPI native binary wheels
- Keyless OIDC Trusted Publishing for crates.io, npm (with provenance), and PyPI β no stored registry tokens
- Artifactory, Cloudsmith, Fury, Docker Hub
- Custom publisher commands
Announce
- Discord, Slack, Telegram, Teams, Mattermost
- Email, Reddit, Twitter/X, Mastodon, Bluesky, LinkedIn
- OpenCollective, Discourse
- Generic webhooks with custom headers and templates
Advanced
- Tera templates (Jinja2-like) with GoReleaser-compatible
{{ .Field }} syntax
- Nightly builds with date-based versioning
- Config includes for shared configuration
- Split/merge CI for fan-out parallel builds
- Monorepo support with independent workspaces
- Auto-tagging from commit message directives
- Reproducible builds with
mod_timestamp and builds_info
- Version-string file syncing (
version_files) to keep docs, scripts, and manifests in lockstep at tag
- Cross-publisher track promotion (
anodizer promote) β flip an already-published artifact to a stable track (snapcraft channels, npm dist-tags, OCI floating tags, GitHub prerelease) with no rebuild
- Post-release verification with install smoke tests
- JSON Schema for editor autocomplete and validation
Installation
Homebrew (macOS/Linux)
brew install tj-smith47/tap/anodizer
Cargo
From source
git clone https://github.com/tj-smith47/anodizer.git
cd anodizer
cargo install --path crates/cli
Quick Start
# 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
# Bump crate versions from Conventional Commits β rewrites Cargo.toml + Cargo.lock,
# no tag (tag separately, or let the release workflow tag for you)
anodizer bump --dry-run --output json # preview the per-crate plan as JSON
anodizer bump # infer patch/minor/major per crate since its last tag
anodizer bump minor --package mycrate # force a level for one crate
anodizer bump --workspace --commit --changelog # bump every member, commit, refresh CHANGELOG.md
anodizer bump --pre rc.1 # append a prerelease identifier (0.5.0-rc.1)
# 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 --push --changelog # tag + refresh changelog + push (triggers the release)
anodizer tag --sign --push # signed annotated tag (uses git user.signingkey)
anodizer tag --custom-tag v0.1.0 # force a tag value, bypassing bump derivation
anodizer tag --version 1.2.3 --crate mycrate # pin an exact version for recovery (per-crate mode)
anodizer tag rollback "$GITHUB_SHA" # delete anodize-managed tags at a SHA + revert the bump
# Resume a stalled release from a populated dist/ β no rebuild
anodizer continue # resume the publish-only pipeline after a transient failure
anodizer continue --merge # fan-in split-build workers, then sign/checksum/release/publish
anodizer continue --skip npm,announce # resume but skip named stages/publishers
# Run only the publish stages (release / blob / publish) from a completed dist/
anodizer publish --publishers homebrew,scoop # publish to a subset of configured publishers
anodizer publish --dry-run # preview the publish chain with no side effects