Low-level derivations

Revision as of 19:29, 9 June 2025 by DoggoBit (talk | contribs) (Refine first example)
🏗︎
This article or section is a work in progress. The work on this article started on 9 June 2025 The information on this page may not be reliable, and the formatting may not follow the Manual of Style yet. Please remove this notice as soon as the work is in a presentable shape.

Writing low-level derivations without the convenience of the Standard environment requires understanding the core fundamentals of how Nix works. While writing derivations in this manner is not common, they can be useful when no utility present in the standard environment matches the required use case, as well as being an essential learning tool for understanding why stdenv.mkDerivation is needed in most cases.

In the section below, we'll start by writing a simple derivation that creates a script which displays the message "Hello, world" when run. While developing the Nix file, we'll be encountering the same challenges that led the Nix community to develop more ergonomic tooling. Each obstacle we hit - from missing system dependencies to path management - illustrates a core principle of Nix's purely functional build model. By the end, you'll understand both what makes Nix powerful and why most derivations use abstraction layers.

A Hello, world example

Our goal is simple: creating a script that displays "Hello, world!". If we were to simply create a script ourselves, it might look like this:

≡︎ example.sh
#!/bin/bash
echo "Hello, world!"

When running this script (e.g. $ ./example.sh) we'll get exactly the output we want. However, we're trying to build the script using Nix. Our first approach might be to write a simple derivation similar to:

❄︎ example.nix
derivation {
  name = "hello-world";
  system = builtins.currentSystem;
  builder = "/bin/bash";
  args = [
    "-c"
    "echo '#!/bin/bash' > $out" 
    "echo 'echo \"Hello, World!\"' > $out" 
    "chmod +x $out"
  ];
}

There are a number of elements in this Nix file, but remembering the conceptual model of a framework makes quick work of figuring them out: a derivation is a set of inputs along with an executable that produces a deterministic output, following a list of steps. Here our inputs are quite literally the key-value pairs in the derivation attrset: the name of the derivation and the system it's being built for. The executable (called the builder) is the program found at /bin/bash. And the steps are:

  1. Write the string #!/bin/bash to the output file ($out);
  2. Write the string echo "Hello, World!" to the output file;
  3. Make the output file executable.