Naersk addresses a common frustration for Rust developers using Nix: how to build Rust projects reproducibly without relying on impure hooks or external scripts that break Nix’s purity guarantees. It offers a cargo build equivalent within the Nix ecosystem, parsing Cargo.lock and managing dependencies entirely in Nix expressions. This makes it suitable for sandboxed builds and CI/CD systems like Hydra that reject Impure Function Dependencies (IFD).
what naersk does and how it works
Naersk is a Nix library designed to simplify building Rust projects within Nix by eliminating the need for imperative build steps that call out to cargo during the build. Unlike traditional Nix Rust packages that either vendor dependencies or invoke cargo with impure hooks, Naersk parses your Cargo.lock to directly manage dependencies in Nix. This means it can reproduce builds deterministically using Nix’s sandboxing and caching.
At its core, Naersk provides a buildPackage function that accepts an attribute set defining the Rust project’s parameters, including source paths, dependencies, and optionally custom Rust toolchains. Under the hood, it uses Nix’s functional language to construct derivations reflecting the exact dependency graph from Cargo.lock, avoiding IFD by never invoking cargo during the nix build phase.
It integrates well with Nix flakes and dependency managers like Niv, supporting advanced workflows including Git dependencies and flexible source overrides. By default, Naersk ignores rust-toolchain files and uses the Rust version from nixpkgs, but can be configured to honor custom toolchains via overlays.
The stack is pure Nix with Rust tooling (rustc, cargo) supplied from nixpkgs or overlays. The repo is primarily Nix expressions and modules, focusing on build logic rather than Rust code itself.
technical strengths and tradeoffs
Naersk’s main technical strength is its IFD-free design. Impure Function Dependencies are a known pain point in Nix when cargo downloads or builds dependencies at build time, breaking reproducibility and caching. By parsing Cargo.lock upfront and baking the dependency graph into Nix derivations, Naersk avoids this issue completely.
This design also makes it a better fit for Hydra and other CI/CD systems that enforce strict sandboxing. Builds are fully declarative and cacheable, with no network access or mutable state during build.
The tradeoff is that Naersk requires careful maintenance of the Cargo.lock file and can be less flexible for dynamic dependency graphs that change often. It also means the initial setup can be more involved compared to just calling cargo in a shell environment.
The codebase is surprisingly clean for a Nix project, with well-structured modules and good documentation. It provides fine-grained control for advanced users but remains accessible for typical Rust projects.
Naersk’s support for custom Rust toolchains via nixpkgs overlays is a practical feature, especially for projects requiring nightly or specific Rust versions. However, this adds complexity compared to using rustup or cargo’s native toolchain management.
Overall, Naersk balances purity and reproducibility with flexibility, making it a solid choice if you prioritize deterministic builds in Nix.
quick start with naersk flakes
The README provides a clear quickstart using Nix flakes. Here’s the exact setup excerpt:
$ nix flake init -t github:nix-community/naersk
$ nix flake lock
Alternatively, you can create a flake.nix next to your Cargo.toml and Cargo.lock with this snippet:
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
naersk = {
url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs";
};
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { flake-utils, naersk, nixpkgs, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = (import nixpkgs) {
inherit system;
};
naersk' = pkgs.callPackage naersk {};
in {
# For `nix build` & `nix run`:
packages.default = naersk'.buildPackage {
src = ./.;
};
# For `nix develop` (optional, can be skipped):
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ rustc cargo ];
};
}
);
}
This config assumes your source is in the same directory as the flake file; adjust src = ./. if not. Also, note that by default Naersk ignores the rust-toolchain file unless overridden via overlays.
If you need to use a custom rust-toolchain file, the README includes a more complex example involving nixpkgs-mozilla overlays to pick up the right Rust channel.
verdict
Naersk is a pragmatic solution for Rust developers who want reproducible, sandboxed builds inside Nix without the usual pitfalls of Impure Function Dependencies. It shines in CI/CD environments where deterministic builds and caching are critical.
The main limitation is that it requires a solid understanding of Nix and some upfront configuration, especially if you deviate from stable Rust versions or complex dependency graphs. For simple projects or those not invested in Nix, the overhead may not be worth it.
If you run Hydra or want to integrate Rust builds tightly in your Nix pipelines, Naersk is definitely worth exploring. Its code quality and documentation make it accessible, but expect a learning curve if you’re new to Nix.
In short, Naersk is a tool that solves a real problem with a clear tradeoff: purity and reproducibility at the cost of some complexity and less flexibility compared to cargo native builds. For teams committed to Nix and reproducible builds, it’s a solid choice to consider.
Related Articles
- Hatchet: durable background task orchestration with Go and Postgres — Hatchet offers a durable, fault-tolerant background task and workflow engine built with Go and Postgres. It supports com
- OpenAI Codex CLI: local-first AI coding assistant with ChatGPT integration — OpenAI Codex CLI brings AI coding assistance local to your terminal, integrating with ChatGPT plans for powerful hybrid
→ GitHub Repo: nix-community/naersk ⭐ 975 · Nix