askill
cargo2nix

cargo2nixSafety 90Repository

cargo2nix Skill

10 stars
1.2k downloads
Updated 1/27/2026

Package Files

Loading files...
SKILL.md

cargo2nix Skill

Expert guidance for packaging Rust applications with Nix Granular per-crate builds with reproducible, cacheable dependencies

Overview

cargo2nix is a tool that brings Nix dependency management to Rust projects, enabling reproducible builds, efficient per-crate caching, and complete control over the build environment. Unlike simpler approaches, cargo2nix creates individual Nix derivations for each crate, allowing fine-grained dependency sharing across projects and optimal build caching.

Key Features:

  • Per-Crate Derivations: Each dependency gets its own derivation for optimal caching
  • Reproducible Builds: Pure builds with nixpkgs integration
  • Development Shells: Automatic dev environments with all dependencies
  • Workspace Support: Full Cargo workspace compatibility
  • Cross-Compilation: Target specification for different platforms
  • Build Caching: Skip unnecessary work in CI/CD pipelines

Project Status:

When to Use cargo2nix

cargo2nix vs Other Tools

The Rust/Nix ecosystem offers 8+ different solutions. Understanding when to use cargo2nix is critical:

ToolDerivationsGenerated FilesBest ForTrade-offs
buildRustPackage1 (monolithic)NoneSimple projects, nixpkgs integrationNo dependency caching
naersk2 (deps + project)NoneBetter caching than buildRustPackageLess granular than cargo2nix
craneComposableNoneModern approach, Cargo-drivenBundles dependencies together
crate2nixPer-crateYes (Cargo.nix)Fine-grained cachingExperimental cross-compilation
cargo2nixPer-crateYes (Cargo.nix)Shared deps, custom toolchainsRequires Cargo.nix maintenance
dream2nixVariesVariesMulti-language projectsAdditional abstraction layer

✅ Use cargo2nix When

  1. Shared Dependencies Across Projects

    • Multiple Rust projects share common crates
    • Want to cache dependencies in binary cache
    • Building a monorepo with multiple Rust packages
  2. Fine-Grained Build Control

    • Need per-crate customization and overrides
    • Complex build.rs scripts require special handling
    • Platform-specific dependency management
  3. Advanced Caching Requirements

    • CI/CD pipelines benefit from per-crate caching
    • Updating one dependency shouldn't rebuild everything
    • Binary cache size matters
  4. Custom Rust Toolchains

    • Need specific Rust versions not in nixpkgs
    • Using nightly features or specific dates
    • Require rust-analyzer, clippy, miri, etc.

❌ Use Alternatives When

Use buildRustPackage if:

  • Simple project with few dependencies
  • Want nixpkgs native integration
  • Don't need fine-grained caching

Use crane if:

  • Want modern, composable approach
  • Prefer Cargo-driven builds
  • Don't need per-crate granularity

Use naersk if:

  • Need simple dependency separation
  • Want dynamic Cargo.lock importing
  • Two-derivation model is sufficient

Use crate2nix if:

  • devenv recommends it (as of 2025)
  • Want per-crate builds without custom toolchains
  • Experimental cross-compilation is acceptable

Installation

Prerequisites

  • Nix with flakes enabled
  • Existing Rust project with Cargo.toml and Cargo.lock

Quick Start

Generate Cargo.nix (one-time):

# Using nix run
nix run github:cargo2nix/cargo2nix

# Or in development shell
nix develop github:cargo2nix/cargo2nix#bootstrap
cargo2nix

# Commit the generated file
git add Cargo.nix
git commit -m "Add Cargo.nix for cargo2nix"

Update Cargo.nix (when Cargo.lock changes):

nix run github:cargo2nix/cargo2nix
git add Cargo.nix
git commit -m "Update Cargo.nix"

Version Pinning

Specify cargo2nix version in flake.nix:

{
  inputs = {
    # Latest release (recommended)
    cargo2nix.url = "github:cargo2nix/cargo2nix";

    # Specific version (stable)
    cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.12";

    # Development version (unstable)
    cargo2nix.url = "github:cargo2nix/cargo2nix/main";
  };
}

Update specific version:

nix flake lock --update-input cargo2nix

Basic Usage

Minimal Flake Configuration

{
  description = "Rust project using cargo2nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    cargo2nix = {
      url = "github:cargo2nix/cargo2nix/release-0.12";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, cargo2nix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ cargo2nix.overlays.default ];
        };

        # Build Rust package set
        rustPkgs = pkgs.rustBuilder.makePackageSet {
          rustVersion = "1.75.0";
          packageFun = import ./Cargo.nix;
        };

      in {
        packages = {
          # Expose your binary
          myapp = rustPkgs.workspace.myapp {};
          default = self.packages.${system}.myapp;
        };

        # Development shell
        devShells.default = rustPkgs.workspaceShell {
          packages = with pkgs; [
            rust-analyzer
            cargo-watch
            cargo-edit
          ];
        };
      }
    );
}

Build and Run

# Build your project
nix build

# Run the binary
./result/bin/myapp

# Enter development shell
nix develop

# Inside dev shell
cargo build
cargo test
cargo run

Workspace Support

Multi-Crate Workspace

cargo2nix fully supports Cargo workspaces:

# Cargo.toml (workspace root)
[workspace]
members = [
  "crates/mylib",
  "crates/mybin",
  "crates/common",
]

Flake Configuration:

{
  outputs = { self, nixpkgs, cargo2nix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ cargo2nix.overlays.default ];
        };

        rustPkgs = pkgs.rustBuilder.makePackageSet {
          rustVersion = "1.75.0";
          packageFun = import ./Cargo.nix;

          # Workspace-wide features
          rootFeatures = [ "mylib/default" "mybin/cli" ];
        };

      in {
        packages = {
          # Expose individual workspace members
          mylib = rustPkgs.workspace.mylib {};
          mybin = rustPkgs.workspace.mybin {};
          common = rustPkgs.workspace.common {};

          # Default to main binary
          default = self.packages.${system}.mybin;
        };

        devShells.default = rustPkgs.workspaceShell {
          packages = with pkgs; [
            rust-analyzer
            cargo-nextest  # Modern test runner
          ];
        };
      }
    );
}

Accessing Individual Crates

# Workspace crates
rustPkgs.workspace.myapp {}
rustPkgs.workspace.mylib {}

# Registry crates (dependencies)
rustPkgs."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.193" {}
rustPkgs."registry+https://github.com/rust-lang/crates.io-index".tokio."1.35.1" {}

# Git dependencies
rustPkgs."git+https://github.com/user/repo".mycrate."0.1.0" {}

makePackageSet Configuration

Core Arguments

rustPkgs = pkgs.rustBuilder.makePackageSet {
  # Rust version (REQUIRED)
  rustVersion = "1.75.0";  # Specific version
  # OR
  rustVersion = "2024-01-15";  # Nightly date

  # Rust channel (default: "stable")
  rustChannel = "stable";    # or "beta", "nightly"

  # Rust profile (default: "default")
  rustProfile = "minimal";   # or "default" (includes docs, rustfmt)

  # Additional components
  extraRustComponents = [
    "rust-analyzer"
    "clippy"
    "rustfmt"
    "miri"
  ];

  # Package function (REQUIRED)
  packageFun = import ./Cargo.nix;

  # Workspace source override
  workspaceSrc = ./.;  # Default: uses Cargo.toml location

  # Root features
  rootFeatures = [
    "default"
    "mylib/serde"
    "mybin/cli"
  ];

  # Package overrides
  packageOverrides = pkgs: [
    (pkgs.rustBuilder.rustLib.makeOverride {
      name = "openssl-sys";
      overrideAttrs = drv: {
        propagatedBuildInputs = drv.propagatedBuildInputs or [] ++ [
          pkgs.openssl
          pkgs.pkg-config
        ];
      };
    })
  ];

  # Cross-compilation target
  target = "x86_64-unknown-linux-musl";
};

Common Rust Versions

# Stable (specific version)
rustVersion = "1.75.0";

# Stable (latest from nixpkgs)
rustVersion = pkgs.rustc.version;

# Nightly (specific date)
rustVersion = "2024-01-15";
rustChannel = "nightly";

# Beta
rustChannel = "beta";

# Using rust-overlay for latest nightly
inputs.rust-overlay.url = "github:oxalica/rust-overlay";

rustToolchain = pkgs.rust-bin.nightly.latest.default.override {
  extensions = [ "rust-src" "rust-analyzer" ];
};

Package Overrides

Common System Dependencies

packageOverrides = pkgs: pkgs.rustBuilder.overrides.all ++ [
  # OpenSSL
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "openssl-sys";
    overrideAttrs = drv: {
      propagatedBuildInputs = drv.propagatedBuildInputs or [] ++ [
        pkgs.openssl
        pkgs.pkg-config
      ];
    };
  })

  # libsqlite3
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "libsqlite3-sys";
    overrideAttrs = drv: {
      propagatedBuildInputs = drv.propagatedBuildInputs or [] ++ [
        pkgs.sqlite
      ];
    };
  })

  # onig (for syntect)
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "onig_sys";
    overrideAttrs = drv: {
      buildInputs = drv.buildInputs or [] ++ [ pkgs.oniguruma ];
    };
  })

  # PostgreSQL
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "pq-sys";
    overrideAttrs = drv: {
      propagatedBuildInputs = drv.propagatedBuildInputs or [] ++ [
        pkgs.postgresql
      ];
    };
  })

  # libgit2
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "libgit2-sys";
    overrideAttrs = drv: {
      buildInputs = drv.buildInputs or [] ++ [
        pkgs.libgit2
        pkgs.pkg-config
      ];
    };
  })
];

Override Patterns

Use Existing Overrides:

packageOverrides = pkgs: pkgs.rustBuilder.overrides.all;

Combine with Custom Overrides:

packageOverrides = pkgs: pkgs.rustBuilder.overrides.all ++ [
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "my-custom-crate";
    overrideAttrs = drv: {
      # Custom build dependencies
      nativeBuildInputs = drv.nativeBuildInputs or [] ++ [
        pkgs.cmake
        pkgs.protobuf
      ];

      # Environment variables
      LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";

      # Pre-build script
      preConfigure = ''
        export CUSTOM_VAR="value"
      '';
    };
  })
];

Platform-Specific Overrides

packageOverrides = pkgs: [
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "ring";
    overrideAttrs = drv: {
      # Only on aarch64-darwin
      buildInputs = drv.buildInputs or [] ++
        pkgs.lib.optionals pkgs.stdenv.isDarwin [
          pkgs.darwin.apple_sdk.frameworks.Security
        ];
    };
  })
];

Cross-Compilation

Basic Cross-Compilation

rustPkgs = pkgs.rustBuilder.makePackageSet {
  rustVersion = "1.75.0";
  packageFun = import ./Cargo.nix;

  # Cross-compile to x86_64 musl (static binary)
  target = "x86_64-unknown-linux-musl";
};

Common Targets

# Static Linux binary (musl)
target = "x86_64-unknown-linux-musl";

# ARM 64-bit Linux
target = "aarch64-unknown-linux-gnu";

# Windows
target = "x86_64-pc-windows-gnu";

# macOS ARM (M1/M2)
target = "aarch64-apple-darwin";

# WebAssembly
target = "wasm32-unknown-unknown";

Multi-Target Builds

{
  outputs = { self, nixpkgs, cargo2nix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ cargo2nix.overlays.default ];
        };

        mkApp = target:
          (pkgs.rustBuilder.makePackageSet {
            rustVersion = "1.75.0";
            packageFun = import ./Cargo.nix;
            inherit target;
          }).workspace.myapp {};

      in {
        packages = {
          default = mkApp null;  # Native
          linux-musl = mkApp "x86_64-unknown-linux-musl";
          linux-arm = mkApp "aarch64-unknown-linux-gnu";
          windows = mkApp "x86_64-pc-windows-gnu";
        };
      }
    );
}

Development Shells

Basic Shell

devShells.default = rustPkgs.workspaceShell {
  packages = with pkgs; [
    # Development tools
    rust-analyzer
    cargo-watch
    cargo-edit
    cargo-udeps
    cargo-audit

    # Additional utilities
    just          # Command runner
    bacon         # Background Rust compiler
  ];
};

Enhanced Shell with Environment

devShells.default = rustPkgs.workspaceShell {
  packages = with pkgs; [
    rust-analyzer
    cargo-nextest
    cargo-llvm-cov  # Code coverage
  ];

  shellHook = ''
    echo "🦀 Rust development environment loaded"
    echo "Rust version: ${rustPkgs.rustVersion}"

    # Set up environment
    export RUST_BACKTRACE=1
    export RUST_LOG=debug

    # Aliases
    alias t='cargo nextest run'
    alias b='cargo build'
    alias r='cargo run'
    alias c='cargo check'

    # Display workspace info
    echo ""
    echo "Workspace members:"
    cargo metadata --no-deps | jq -r '.workspace_members[]'
  '';
};

Shell with Database

devShells.default = rustPkgs.workspaceShell {
  packages = with pkgs; [
    rust-analyzer
    postgresql_15
    diesel-cli
  ];

  shellHook = ''
    # Set up PostgreSQL
    export PGDATA="$PWD/.pgdata"
    export DATABASE_URL="postgresql://localhost/myapp_dev"

    if [ ! -d "$PGDATA" ]; then
      echo "Initializing PostgreSQL database..."
      initdb --no-locale --encoding=UTF8
      echo "unix_socket_directories = '$PWD/.pgdata'" >> "$PGDATA/postgresql.conf"
      pg_ctl start -l "$PGDATA/server.log"
      psql -d postgres -c "CREATE DATABASE myapp_dev;"
    else
      pg_ctl start -l "$PGDATA/server.log"
    fi

    echo "Database ready at $DATABASE_URL"
  '';
};

Debugging Builds

Isolated Build Environment

Enter the exact build environment cargo2nix uses:

# Get derivation path
nix build --print-out-paths .#myapp

# Enter isolated build environment
nix develop --ignore-environment /nix/store/...-myapp-0.1.0

# Inside, run build hooks
runHook preConfigure
runHook configureCargo
runHook runCargo

Common Debug Commands

# Check what Cargo.nix was generated from
head -n 20 Cargo.nix

# Verify Cargo.lock hash matches
nix eval .#rustPkgs.workspace.myapp.cargoLockHash

# Inspect package set
nix repl
> :lf .
> rustPkgs.workspace
> rustPkgs."registry+https://github.com/rust-lang/crates.io-index".serde

# Build with verbose output
nix build -L  # Show build logs
nix log .#myapp  # View previous build log

Debugging Overrides

# Add debug output to overrides
packageOverrides = pkgs: [
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "problematic-crate";
    overrideAttrs = drv: {
      # Print all environment variables
      preBuild = ''
        echo "=== Build Environment ==="
        env | sort
        echo "=== Build Inputs ==="
        echo "nativeBuildInputs: $nativeBuildInputs"
        echo "buildInputs: $buildInputs"
      '';
    };
  })
];

Troubleshooting

1. Cargo.lock Needs Update

Problem: Error: "Cargo.lock needs to be updated but --locked was passed"

Solution:

# Update Cargo.lock
cargo update

# Regenerate Cargo.nix
nix run github:cargo2nix/cargo2nix

# Commit both files
git add Cargo.lock Cargo.nix
git commit -m "Update dependencies"

2. Hash Mismatch

Problem: Cargo.nix hash doesn't match Cargo.lock

Temporary Workaround:

rustPkgs = pkgs.rustBuilder.makePackageSet {
  rustVersion = "1.75.0";
  packageFun = import ./Cargo.nix;

  # Disable hash check (NOT RECOMMENDED for production)
  ignoreLockHash = true;
};

Proper Solution:

# Regenerate Cargo.nix from current Cargo.lock
nix run github:cargo2nix/cargo2nix

3. System Dependencies Missing

Problem: build.rs fails with "library not found"

Solution: Add package override

packageOverrides = pkgs: pkgs.rustBuilder.overrides.all ++ [
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "failing-crate-sys";
    overrideAttrs = drv: {
      propagatedBuildInputs = drv.propagatedBuildInputs or [] ++ [
        pkgs.the-missing-library
        pkgs.pkg-config
      ];
    };
  })
];

4. Git Dependencies with Private Repos

Problem: Can't fetch private git dependencies

Solution:

# Use builtins.fetchGit with SSH
packageOverrides = pkgs: [
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "private-crate";
    overrideAttrs = drv: {
      src = builtins.fetchGit {
        url = "git@github.com:org/private-repo.git";
        ref = "main";
        rev = "abc123...";
      };
    };
  })
];

Note: Cannot use with --restrict-eval

5. Non-Deterministic Builds

Problem: Binary output differs between builds

Solution:

rustPkgs.workspace.myapp {
  # Prefer local build (don't use binary cache)
  preferLocalBuild = true;
}

6. TOML Parsing Errors

Problem: Complex cfg() attributes fail to parse

Solution: Use newer cargo2nix version

cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.12";

7. Rust Version Not Available

Problem: Specific Rust version not in nixpkgs

Solution: Use rust-overlay

{
  inputs = {
    rust-overlay.url = "github:oxalica/rust-overlay";
  };

  outputs = { nixpkgs, rust-overlay, cargo2nix, ... }:
    let
      pkgs = import nixpkgs {
        overlays = [
          rust-overlay.overlays.default
          cargo2nix.overlays.default
        ];
      };

      rustPkgs = pkgs.rustBuilder.makePackageSet {
        # Use rust-overlay for any version
        rustToolchain = pkgs.rust-bin.stable."1.75.0".default;
        packageFun = import ./Cargo.nix;
      };
    in { ... };
}

8. Build Performance Issues

Problem: Slow builds in CI/CD

Solutions:

# 1. Use binary cache
nix.settings = {
  substituters = [ "https://cache.nixos.org" ];
  trusted-public-keys = [ "cache.nixos.org-1:..." ];
};

# 2. Enable distributed builds
nix.distributedBuilds = true;
nix.buildMachines = [ ... ];

# 3. Optimize build cores
nix.settings = {
  max-jobs = "auto";
  cores = 0;  # Use all cores
};

Golden Path: Best Practices

1. ✅ Always Commit Cargo.nix

Do: Version control the generated file

git add Cargo.nix
git commit -m "Add/update Cargo.nix"

Why: Essential for reproducible builds, team collaboration

2. ✅ Pin cargo2nix Version

Do: Use specific release branch

cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.12";

Why: Avoid breaking changes, ensure stability

3. ✅ Use Common Overrides First

Do: Start with built-in overrides

packageOverrides = pkgs: pkgs.rustBuilder.overrides.all ++ [
  # Your custom overrides
];

Why: Handles 90% of common system dependencies

4. ✅ Regenerate After Cargo.lock Changes

Do: Update Cargo.nix when dependencies change

cargo update
nix run github:cargo2nix/cargo2nix
git add Cargo.lock Cargo.nix

Why: Keeps Nix expressions in sync with Cargo

5. ✅ Use workspaceShell for Development

Do: Leverage generated dev shell

devShells.default = rustPkgs.workspaceShell {
  packages = [ pkgs.rust-analyzer ];
};

Why: Automatically includes all dependencies

6. ✅ Test Cross-Compilation Early

Do: Add cross-compile targets in CI

packages = {
  default = rustPkgs.workspace.myapp {};
  static = mkApp "x86_64-unknown-linux-musl";
};

Why: Catch platform issues before release

7. ✅ Document Custom Overrides

Do: Add comments explaining why overrides exist

packageOverrides = pkgs: [
  # Required for OpenSSL 3.0 compatibility
  (pkgs.rustBuilder.rustLib.makeOverride {
    name = "openssl-sys";
    # ...
  })
];

Why: Future maintainers understand decisions

8. ✅ Use Flake Lock for Reproducibility

Do: Commit flake.lock

git add flake.lock
git commit -m "Lock dependencies"

Why: Ensures identical builds across machines

9. ✅ Separate Dev Dependencies

Do: Use dev-dependencies in Cargo.toml

[dev-dependencies]
criterion = "0.5"  # Benchmarking
proptest = "1.4"   # Property testing

Why: Production builds exclude dev dependencies

10. ✅ Validate Builds in CI

Do: Test nix builds in CI pipeline

- name: Build with Nix
  run: nix build -L
- name: Run tests
  run: nix develop -c cargo test

Why: Catch Nix-specific issues early

Anti-Patterns: What to Avoid

1. ❌ Forgetting to Regenerate Cargo.nix

Don't: Update Cargo.lock without updating Cargo.nix

# Bad
cargo update
git add Cargo.lock
git commit  # Missing Cargo.nix update!

Why: Builds will fail with hash mismatches

Do Instead:

cargo update
nix run github:cargo2nix/cargo2nix
git add Cargo.lock Cargo.nix

2. ❌ Using ignoreLockHash in Production

Don't: Disable hash verification

# Bad: Disables safety checks
rustPkgs = pkgs.rustBuilder.makePackageSet {
  ignoreLockHash = true;
};

Why: Loses reproducibility guarantees

Do Instead: Fix root cause (regenerate Cargo.nix)

3. ❌ Not Pinning cargo2nix Version

Don't: Use floating reference

# Bad: Could break unexpectedly
cargo2nix.url = "github:cargo2nix/cargo2nix";

Why: Unstable, breaking changes

Do Instead: Pin to release branch

cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.12";

4. ❌ Overriding Everything Manually

Don't: Ignore existing overrides

# Bad: Reinventing the wheel
packageOverrides = pkgs: [
  # Manually writing openssl-sys override...
];

Why: Duplicates work, misses updates

Do Instead: Use pkgs.rustBuilder.overrides.all

5. ❌ Committing Build Artifacts

Don't: Track result symlinks

# Bad
git add result

Why: Not reproducible, wastes space

Do Instead: Add to .gitignore

result
result-*

6. ❌ Mixing rustup and Nix

Don't: Use rustup inside nix shell

# Bad: In nix develop
rustup install nightly

Why: Defeats Nix reproducibility

Do Instead: Use cargo2nix's rustVersion/rustToolchain

7. ❌ Hardcoding Paths

Don't: Use absolute paths in overrides

# Bad: Not portable
LIBCLANG_PATH = "/usr/lib/llvm-15/lib";

Why: Breaks on other systems

Do Instead: Use Nix packages

LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";

8. ❌ Skipping Tests in CI

Don't: Only build, never test

# Bad: No testing
- run: nix build

Why: Misses test failures

Do Instead: Run tests in Nix environment

- run: nix develop -c cargo test

9. ❌ Using --impure Unnecessarily

Don't: Rely on impure evaluation

# Bad: Breaks reproducibility
nix build --impure

Why: Not reproducible, defeats Nix purpose

Do Instead: Fix evaluation to be pure

10. ❌ Ignoring Build Warnings

Don't: Suppress or ignore warnings

# Bad: Hiding problems
RUSTFLAGS = "-A warnings"

Why: Masks real issues

Do Instead: Fix warnings, use deny in CI

RUSTFLAGS = "-D warnings"  # Deny warnings

Complete Examples

Example 1: Simple CLI Tool

Project Structure:

my-cli/
├── Cargo.toml
├── Cargo.lock
├── Cargo.nix          # Generated
├── flake.nix
├── flake.lock
└── src/
    └── main.rs

Cargo.toml:

[package]
name = "my-cli"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4.4", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }

flake.nix:

{
  description = "My CLI tool";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.12";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, cargo2nix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ cargo2nix.overlays.default ];
        };

        rustPkgs = pkgs.rustBuilder.makePackageSet {
          rustVersion = "1.75.0";
          packageFun = import ./Cargo.nix;
        };

      in {
        packages = {
          my-cli = rustPkgs.workspace.my-cli {};
          default = self.packages.${system}.my-cli;
        };

        devShells.default = rustPkgs.workspaceShell {
          packages = with pkgs; [ rust-analyzer ];
        };

        apps.default = {
          type = "app";
          program = "${self.packages.${system}.my-cli}/bin/my-cli";
        };
      }
    );
}

Usage:

# Generate Cargo.nix
nix run github:cargo2nix/cargo2nix
git add Cargo.nix

# Build
nix build

# Run
nix run

# Development
nix develop
cargo run -- --help

Example 2: Workspace with Multiple Binaries

Project Structure:

my-workspace/
├── Cargo.toml         # Workspace root
├── Cargo.lock
├── Cargo.nix          # Generated
├── flake.nix
├── crates/
│   ├── server/
│   │   ├── Cargo.toml
│   │   └── src/main.rs
│   ├── client/
│   │   ├── Cargo.toml
│   │   └── src/main.rs
│   └── common/
│       ├── Cargo.toml
│       └── src/lib.rs
└── flake.lock

flake.nix:

{
  description = "My multi-binary workspace";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.12";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, cargo2nix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ cargo2nix.overlays.default ];
        };

        rustPkgs = pkgs.rustBuilder.makePackageSet {
          rustVersion = "1.75.0";
          packageFun = import ./Cargo.nix;
          rootFeatures = [
            "server/default"
            "client/default"
            "common/default"
          ];
        };

      in {
        packages = {
          server = rustPkgs.workspace.server {};
          client = rustPkgs.workspace.client {};
          common = rustPkgs.workspace.common {};

          default = self.packages.${system}.server;

          # Bundle both binaries
          all = pkgs.symlinkJoin {
            name = "my-workspace-all";
            paths = [
              self.packages.${system}.server
              self.packages.${system}.client
            ];
          };
        };

        devShells.default = rustPkgs.workspaceShell {
          packages = with pkgs; [
            rust-analyzer
            cargo-nextest
            cargo-watch
          ];
        };

        apps = {
          server = {
            type = "app";
            program = "${self.packages.${system}.server}/bin/server";
          };
          client = {
            type = "app";
            program = "${self.packages.${system}.client}/bin/client";
          };
        };
      }
    );
}

Example 3: With System Dependencies

flake.nix (PostgreSQL + OpenSSL):

{
  outputs = { self, nixpkgs, cargo2nix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ cargo2nix.overlays.default ];
        };

        rustPkgs = pkgs.rustBuilder.makePackageSet {
          rustVersion = "1.75.0";
          packageFun = import ./Cargo.nix;

          # System dependency overrides
          packageOverrides = pkgs: pkgs.rustBuilder.overrides.all ++ [
            # PostgreSQL native library
            (pkgs.rustBuilder.rustLib.makeOverride {
              name = "pq-sys";
              overrideAttrs = drv: {
                propagatedBuildInputs = drv.propagatedBuildInputs or [] ++ [
                  pkgs.postgresql
                ];
              };
            })

            # OpenSSL
            (pkgs.rustBuilder.rustLib.makeOverride {
              name = "openssl-sys";
              overrideAttrs = drv: {
                propagatedBuildInputs = drv.propagatedBuildInputs or [] ++ [
                  pkgs.openssl
                  pkgs.pkg-config
                ];
              };
            })
          ];
        };

      in {
        packages.default = rustPkgs.workspace.myapp {};

        devShells.default = rustPkgs.workspaceShell {
          packages = with pkgs; [
            rust-analyzer
            postgresql_15
            diesel-cli
          ];

          shellHook = ''
            export DATABASE_URL="postgresql://localhost/myapp_dev"
            echo "Database: $DATABASE_URL"
          '';
        };
      }
    );
}

Resources and References

Official Documentation

Related Tools

Community Resources

Learning Resources


Remember: cargo2nix provides fine-grained per-crate builds at the cost of maintaining Cargo.nix. For simpler projects, consider buildRustPackage or crane. For maximum caching and shared dependencies across projects, cargo2nix is the right choice.

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

95/100Analyzed 2/4/2026

An exceptionally thorough technical guide for cargo2nix, providing clear comparisons, configuration templates, and troubleshooting advice for Rust/Nix integration.

90
100
95
100
95

Metadata

Licenseunknown
Version-
Updated1/27/2026
Publisherolafkfreund

Tags

ci-cddatabasegithubsecuritytesting