Open Source Docs

Sbtix

Reproducible Nix builds for sbt projects

Design Overview

How dependency locking and Nix composition fit together


Design Overview

When you run sbtix genNix / genComposition, the plugin orchestrates three steps:

  1. Collect dependencies: Sbtix walks your sbt build, resolves everything with Coursier (using your resolvers + credentials), and emits a locked repo.nix. Private repositories keep their own namespace (e.g. nix-private-demo) so you can see where each artifact originates. For Scala 2.13 and Scala 3 builds, sbtix also locks sbt tooling such as org.scala-lang:scala2-sbt-bridge / org.scala-lang:scala3-sbt-bridge so sbt can compile offline in the Nix sandbox.

  2. Generate the composition: The plugin now renders sbtix-generated.nix, the machine-written derivation that wires together repo.nix, the plugin bootstrap, and the staging/install logic. If there is no default.nix in your project yet, sbtix drops in a tiny example that simply imports ./sbtix-generated.nix. Your own default.nix is never overwritten again.

  3. Consume from Nix: Import the generated file (import ./sbtix-generated.nix { inherit pkgs; }) from your project’s default.nix, or call the helpers in plugin/nix-exprs/sbtix.nix (buildSbtProgram, buildSbtLibrary, etc.) if you need a different layout. Either way, the derivation reads the locked repo.nix files and the staged application copied in step 2, so builds are reproducible and network-free. The Nix build environment also sets SBTIX_NIX_BUILD=1 and -Dsbtix.nixBuild=true so your build can disable tasks that would otherwise try to download tooling (e.g. scalafmt).

This pipeline keeps your sbt workflow fast (dependency metadata is cached) while ensuring the Nix derivation is always in sync with what the sbt plugin produces.