Creating a static nix channel with binary cache

TL;DR: full example at https://github.com/lpenz/nixpkgs-lpenz/

Nix is a packet manager for Unix-like systems that can be effectively used as an overlay distribution.

These are my notes on how to create a static nix channel with a binary cache that can be used to distribute compiled packages - and how to do it using travis-ci and github pages. This assumes that we already have the derivations that we want to distribute.

You should also check out Cachix, as an alternative to what is described here.

Generating key pair

The first step in creating a repository is generating a key pair:

  nix-store --generate-binary-cache-key lpenz.org cache-priv-key.pem cache-pub-key.pem

That creates the single-line files cache-priv-key.pem and cache-pub-key.pem. The first has the private key, which we will use to do the packaging process in travis-ci (store it in a safe place). The second file has the public key that we will use to authenticate the packages in the clients.

Setting up the repository in github and travis-ci

This article is not going into the details of how to create a repository and populate it with nix derivations. Here are some references on that:

To set up github pages, go to the Settings page of the github repsitory with the derivations, GitHub Pages and set Source to the gh-pages branch.

To set up travis-ci, create a NIX_CACHE_PRIV_KEY environment variable in the settings (details), with the contents of cache-priv-key.pem. Keep in mind the best practices at Recommendations on how to avoid leaking secrets to build logs to avoid problems.

Creating the channel

A channel is simply a nixexprs.tar.xz with all nix derivation files, and a binary-cache-url file that, as the name implies, points to the binary cache URL.

Let's assume that we are creating the channel in the _output directory, and that we will put the cache files in _output/cache. To create the nixexprs.tar.xz file:

  tar -cJf _output/nixexprs.tar.xz ./*.nix \
      --transform "s,^,${PWD##*/}/," \
      --owner=0 --group=0 --mtime="1970-01-01 00:00:00 UTC"

Creating the binary-cache-url file is simpler:

  echo '<URL of cache>' > _output/binary-cache-url

Nix also tries to retrieve the channel URL by itself - create a index.html file to handle that:

  touch _output/index.html

Creating the binary cache

To create the binary cache, start by building the packages with:

  nix-build

Then sign the results:

  export NIX_SECRET_KEY_FILE="$PWD/nix-cache-priv-key.pem"
  echo "$NIX_CACHE_PRIV_KEY" > "$NIX_SECRET_KEY_FILE"
  nix sign-paths

And finally, copy the results from the store to the cache directory:

  nix copy --to "file:///$PWD/_output/cache"

.travis.yml deploy section

Detailed instructions on how to set this up are at GitHub Pages Deployment. We essentially have to set up a GITHUB_TOKEN.

The deploy section of .travis.yml ends up looking like the following:

  deploy:
    provider: pages
    skip-cleanup: true
    github-token: "$GITHUB_TOKEN"
    local_dir: _output
    target-branch: gh-pages
    on:
      branch: master

Using the channel

To use the channel, add the channel URL to nix:

  nix-channel --add <URL of channel>
  nix-channel --update

That allows us to build packages from the channel, but it doesn't make nix use the binary cache. To enable the cache, we have to set up the channel as a substituter. To do that, put the following in ~/.config/nix/nix.conf, creating the file it if it doesn't exist:

  substituters = https://cache.nixos.org <URL of cache>
  trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= <cache-pub-key.pem contents>

Note that we have to also add the default, as that line is an absolute configuration.

Conclusions

Nix is very versatile, and one way to use it is an overlay distribution, by installing nixpkgs over an existing non-NixOS linux installation. On that setup, a private nix channel can be very useful to develop and deploy software consistently without being pegged to a particular gloal distribution or environment - and the binary cache allows us to do that without even having to recompile from source. That's how I've been using it, and I'm quite happy with the results.