~/ ~/documents ~/software ~/pictures github (opens in new tab)

The Self-Referential Let: Orchestrating Scripts with passthru

In the Nix ecosystem, we often talk about the purity of derivations. But once you’ve built your software, you frequently need a set of tools to interact with it—scripts for deployment, database migrations, or local development servers.

The traditional way is to define these as separate packages. However, there’s a powerful “hack” using the let ... in syntax that allows you to bundle these scripts directly within a package’s passthru attribute, while allowing them to reference each other and the package itself.

The Problem: Chicken and Egg

Imagine you have a derivation pkg that builds a static site. You want to add a deploy script to its passthru. That deploy script needs to know the path to the built site (pkg) and perhaps needs to run an init script first.

If you try to do this inside stdenv.mkDerivation using rec { ... }, you’ll quickly hit a wall because rec doesn’t play well with the way mkDerivation merges attributes.

The Hack: Recursive let

The solution lies in the fact that let bindings in Nix are inherently recursive. This means a variable defined in a let block can reference itself or other variables in the same block.

By defining your package as a variable in a let block, you can reference that variable name inside the derivation’s own definition:

let
  pkg = stdenv.mkDerivation {
    pname = "my-site";
    # ...
    
    passthru = {
      init = writeShellScriptBin "init" ''
        echo "Initializing infrastructure..."
      '';

      plan = writeShellScriptBin "plan" ''
        # We reference pkg.init here!
        ${pkg.init}/bin/init
        echo "Planning deployment..."
      '';

      deploy = writeShellScriptBin "deploy" ''
        # We reference pkg.plan and the package itself!
        ${pkg.plan}/bin/plan
        cp -r ${pkg} /var/www/html
      '';
    };
  };
in
pkg

Why This is Brilliant

  1. Logical Grouping: All the operational logic for a package stays with the package. If you have the package in your environment, you have the scripts.
  2. Dependency Graphing: Nix treats these passthru scripts as dependencies. If the init script changes, only the scripts depending on it need to be updated.
  3. Self-Documentation: Looking at the passthru of a package tells you exactly how to interact with it.

Beyond Scripts: SBOM Generation

Another critical use for this pattern is the automated generation of Software Bills of Materials (SBOMs). By using the self-referential let, you can bundle SBOM generation tools directly into the package’s passthru, ensuring they always run against the exact derivation being built.

I’ve written more about how this simplifies compliance and supply chain integrity in Bill Me Up Boss.

In my own builds, I use this to orchestrate cloud provider authentication, infrastructure-as-code runs, and static site deployments—all chained together through the power of a self-referential let.


Nix allows us to treat our operations as code just as much as our infrastructure. The recursive let is the glue that holds that vision together.