Packaging & Distribution
Ship Rust CLI binaries with cargo-dist, GitHub Releases, and package managers.
Recipe
cargo install cargo-dist
cargo dist init
cargo dist build# dist.toml (generated / customized)
[workspace.metadata.dist]
cargo-dist-version = "0.28.0"
installers = ["shell", "powershell"]
targets = ["aarch64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]When to reach for this: Public CLIs that need reproducible cross-platform release artifacts without manual CI scripting.
Working Example
# Cargo.toml
[package]
name = "mytool"
version = "0.1.0"
edition = "2024"
[workspace.metadata.dist]
# cargo dist reads this for release configuration# .github/workflows/release.yml (simplified)
name: Release
on:
push:
tags: ['v*']
jobs:
dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo dist build --artifacts=local
- uses: softprops/action-gh-release@v2
with:
files: target/distrib/*# User install via shell installer
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/org/mytool/releases/download/v0.1.0/mytool-installer.sh | shWhat this demonstrates:
cargo-distbuilds per-target archives- Install scripts place binaries on
PATH - GitHub Releases hosts versioned artifacts
- Tag-driven workflow keeps releases reproducible
Deep Dive
Distribution Channels
| Channel | Audience |
|---|---|
cargo install | Rust developers |
| GitHub Releases | General users |
| Homebrew / nix | macOS / Linux power users |
| crates.io | Library + binary crate discovery |
Static Linking for Portability
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-muslmusl binaries run on most Linux distros without glibc version issues.
Signing Releases
- Sign checksums with
cosignor GPG - Publish
SHA256SUMSalongside archives - Document verification steps in README
Gotchas
- Missing cross-compilation tools - Linux CI cannot build macOS binaries without cross tooling. Fix: Use cargo-dist's managed build matrix or platform-specific runners.
- Glibc vs musl - Alpine containers need musl targets. Fix: Build separate musl artifacts or use static linking.
- Stripped debug symbols - Harder to diagnose user crashes. Fix: Upload split debug symbols to your crash tracker.
- License files omitted - Redistributors need LICENSE bundled. Fix: Include LICENSE and NOTICE in every archive.
- Version not synced - Tag says v0.2.0 but Cargo.toml says 0.1.0. Fix: Automate version bumps in release workflow.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Manual cross + CI | Full control over build steps | You want installer generation too |
cargo-binstall | Users already use binstall | You need custom install scripts |
| Docker images | Server-side CLI tools | Desktop developer tools |
FAQs
cargo-dist vs cross?
cargo-dist orchestrates releases, installers, and CI. cross focuses on cross-compilation. They complement each other.
How do I publish to crates.io?
cargo publish after cargo dist tag. Binary users can cargo install mytool.
Homebrew formula?
Point the formula at GitHub release tarballs. Update SHA256 on each version bump.
Windows MSI installers?
cargo-dist supports WiX/MSI via its installer options. Enable in dist metadata.
Universal macOS binary?
Build both aarch64-apple-darwin and x86_64-apple-darwin, then lipo or ship separate archives.
SBOM for releases?
Generate with cargo cyclonedx or cargo auditable and attach to releases for supply chain compliance.
Pre-release channels?
Publish beta tags (v0.2.0-beta.1) to a separate GitHub prerelease channel.
Auto-update in the CLI?
Check GitHub Releases API on startup. Optional; respect corporate mirrors.
Minimum Rust version?
Set rust-version in Cargo.toml and enforce in CI so builds match user expectations.
Changelog automation?
Use git-cliff or release-plz to generate notes from conventional commits.
Related
- Shell Completions & Man Pages - bundle completions
- CLI Basics - binary layout
- CLI Best Practices - semver for CLI changes
- Configuration & Env - default config in packages
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.