Nix derivations by hand

Published 2025-03-23 on Farid Zakaria's Blog

My recent posts on dynamic-derivations had me thinking more about working with Nix more directly.

I thought it might be “fun” 🙃 to try and write a derivation by hand, add it to the /nix/store and build it!

Can we even do this? 🤔 Let’s see!

First off, all derivations in the /nix/store are written in this simple but archaic format called ATerm.

Tooling for it is a bit lackluster, so I decided to work purely in JSON!.

Looks like the new nix derivation command can accept JSON rather than the ATerm format.

Okay! Let’s start deriving 🤓

The Nix manual let’s us know that we need 3 required arguments: name, system & builder

{
  "name": "simple",
  "system": "x86_64-linux",
  "builder": "/bin/sh"
}
> nix derivation add < simple.json 
error:
  … while reading key 'outputs'
  error: Expected JSON object to contain key 'outputs'
  but it doesn't...

Okay let’s add an output. I checked the derivation JSON format on the Nix manual to see what it looks like.

I just put some random 32 letter path I came up for now.

{
  "name": "simple",
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "outputs": {
    "out": {
      "path": "/nix/store/7s0z3d6p9y2v5x8b1c4g1w5r2q9n0f8a-simple"
    }
  }
}
> nix derivation add < simple.json
error:
  … while reading key 'inputSrcs'
  error: Expected JSON object to contain
  key 'inputSrcs' but it doesn't:...

Okay, well I don’t want any inputs.. 🤨 Let’s leave it blank for now.

inputSrcs: A list of store paths on which this derivation depends.

{
  "name": "simple",
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "outputs": {
    "out": {
      "path": "/nix/store/7s0z3d6p9y2v5x8b1c4g1w5r2q9n0f8a-simple"
    }
  },
  "inputSrcs": []
}
> nix derivation add < simple.json
error:
  … while reading key 'inputDrvs'
  error: Expected JSON object to contain
  key 'inputDrvs' but it doesn't:...

Let’s keep following this thread and add the missing inputDrvs.

inputDrvs: A JSON object specifying the derivations on which this derivation depends, and what outputs of those derivations.

Turns out we also need env and args. args is particularly useful, since can use it to echo hello world to $out making our derivation meaningful.

{
  "name": "simple",
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "outputs": {
    "out": {
      "path": "/nix/store/7s0z3d6p9y2v5x8b1c4g1w5r2q9n0f8a-simple"
    }
  },
  "inputSrcs": [],
  "inputDrvs": {},
  "env": {},
  "args": [
    "-c",
    "echo 'hello world' > $out"
  ]
}
> nix derivation add < simple.json
error: derivation '/nix/store/03py9f4kw48gk18swsw6g7yjbj21hrsw-simple.drv'
has incorrect output '/nix/store/7s0z3d6p9y2v5x8b1c4g1w5r2q9n0f8a-simple',
should be '/nix/store/hpryci895mgx4cfj6dz81l6a57ih8pql-simple'

That’s helpful! Thank you for telling me the correct hash.

Giving the correct hash will probably be useful for AI-centric workflows, so they can fix their own mistakes. 😂

{
  "name": "simple",
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "outputs": {
    "out": {
      "path": "/nix/store/hpryci895mgx4cfj6dz81l6a57ih8pql-simple"
    }
  },
  "inputSrcs": [],
  "inputDrvs": {},
  "env": {},
  "args": [
    "-c",
    "echo 'hello world' > $out"
  ]
}
> nix derivation add < simple.json
error: derivation '/nix/store/pz7m6zp2hxjldxq8jp846p604qicn73d-simple.drv'
has incorrect environment variable 'out',
should be '/nix/store/hpryci895mgx4cfj6dz81l6a57ih8pql-simple'

Okay this makes sense. I’m using $out in my builder but I never set it to anything in the environment variables. Let’s fix that by adding it to our derivation explicitly.

We will also have to fix our path to be 5bkcqwq3qb6dxshcj44hr1jrf8k7qhxb which Nix will dutifully tell us is the right hash.

{
  "name": "simple",
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "outputs": {
    "out": {
      "path": "/nix/store/5bkcqwq3qb6dxshcj44hr1jrf8k7qhxb-simple"
    }
  },
  "inputSrcs": [],
  "inputDrvs": {},
  "env": {
    "out": "/nix/store/5bkcqwq3qb6dxshcj44hr1jrf8k7qhxb-simple"
  },
  "args": [
    "-c",
    "echo 'hello world' > $out"
  ]
}
> nix derivation add < simple.json
/nix/store/vh5zww1mqbcshfcblrw3y92v7kkzamfx-simple.drv

Huzzah! Nix accepted our derivation. 🎉

Can we build it?

> nix-store --realize /nix/store/vh5zww1mqbcshfcblrw3y92v7kkzamfx-simple.drv
this derivation will be built:
  /nix/store/vh5zww1mqbcshfcblrw3y92v7kkzamfx-simple.drv
building '/nix/store/vh5zww1mqbcshfcblrw3y92v7kkzamfx-simple.drv'...
warning: you did not specify '--add-root'; the result might be removed by the garbage collector
/nix/store/5bkcqwq3qb6dxshcj44hr1jrf8k7qhxb-simple

> cat /nix/store/5bkcqwq3qb6dxshcj44hr1jrf8k7qhxb-simple
hello world

Success! 🤑 We got our expected output as well.

You might be curious why I did /bin/sh instead of something like /bin/bash ?

Well I wanted to keep our derivation extremely simple and even something like bash needs to be an explicit dependency on our derivation.

Turns out though that /bin/sh is by default always present in the Nix sandbox for POSIX compliance. 🤓


Improve this page @ bc4b4d8
The content for this site is CC-BY-SA.