The easiest way (I've found) to create your own Nix channel

Luc Perkins    Monday, August 10, 2020


There are lots of ways of creating Nix channels, but in this post I’d like to highlight the fastest and easiest way I’ve found thus far: via GitHub archives. I discovered this method when installing Home Manager. Nix channels are essentially tarballs that are available via an unchanging URL. Quite conveniently, GitHub has a built-in archive feature that automatically turns every branch in every GitHub repo into a tarball available at a URL with this structure:

https://github.com/<owner>/<repo>/archive/<branch>.tar.gz

This means that you can create and maintain Nix channels without having to do any extra work beyond committing and updating code (thus no extra build process, no CI job, etc.).

I’m using GitHub here because I’m most familiar with it, but plenty of other source control systems offer this same feature. There’s no specific reason to use GitHub for this in principle.

First steps

To begin creating a channel, first create a new repo. Mine will be at lucperkins/nix-channel and I’ll name the channel personal when I use it locally. Substitute your own details, of course. Please note that I’m using main instead of master for my primary branch.

The first thing I’m going to do with this channel is to basically use it as a conduit to nixpkgs but pinned at a specific Git commit. Install Niv and then initialize it in the repo to make that happen:

niv init

This generates a nix directory with a sources.json and a sources.nix. The sources.json provides some metadata about the specific Git revision that nixpkgs is pinned to while sources.nix provides some Nix helpers for consuming the pinned repo. These files are both generated and you should’t modify them, though you do need to commit them to source control.

Now create a default.nix at the root of the repo:

let
  # Import sources
  sources = import ./nix/sources.nix;

# And return that specific nixpkgs
in sources.nixpkgs

Whatever you return in default.nix defines what’s available through the channel. If you commit this and push it to GitHub, you can then add your channel locally:

nix-channel --add https://github.com/lucperkins/nix-channel/archive/main.tar.gz personal
nix-channel --update
nix-channel --list # You should see the channel in the output

Now you can build a specific package from the channel (in this case jq):

nix-build '<personal>' -A jq

Another intriguing possibility is creating separate channels out of different branches. This would enable you to offer, for example, test, staging, and production channels from the same repo. I’m not sure if this is a common use case for channels but it’s worth noting.

Adding an overlay to your channel

Let’s say that you want to offer a pinned nixpkgs via your channel as well as customized versions of some nixpkgs packages. You can do that using Nix overlays. Here’s a channel that overlays the hello package with a custom one:

let
  sources = imports ./nix/sources.nix;

  hello = pkgs.writeShellScriptBin "hello" ''
    echo "Hello from the Nix channel overlay!"
  '';

  pkgs = import sources.nixpkgs {
    overlays = [
      (self: super: {
        inherit hello;
      })
    ];
  };
in pkgs

After you commit and push to GitHub, you can then see the overlayed hello package in action:

nix-channel --update personal
nix-build '<personal>' -A hello
./result/bin/hello
Hello from the Nix channel overlay!

Using the channel

Once you’ve added and updated the channel locally, you can start using it in your own expressions just like you’d use nixpkgs. Here’s a basic example:

let
  pkgs = import <personal>;
in pkgs.mkShell {
  buildInputs = with pkgs; [
    hello
  ];
}

To invoke the hello package via a pure Nix shell:

nix-shell --pure --run "hello"

Once you’d done that, you have officially experienced the magic that is your own Nix channel 😎

Bonus points: use Niv with your channel

Getting a little meta here, we can actually use Niv with our created channel as well. Imagine we’re in an existing Nix project:

niv init
niv add lucperkins/nix-channel --name personal
niv show personal

This will display all the info associated with the channel (owner, branch, repo, sha256, etc.). To use it in an expression. Here’s an example shell.nix:

let
  pkgs = import (import ./nix/sources.nix).personal;
in pkgs.mkShell {
  buildInputs = with pkgs; [
    hello
  ];
}

And now run the hello package:

nix-shell --pure --run "hello"
Hello from the Nix channel overlay!

And now we’ve come full circle from creating a channel using a pinned nixpkgs to consuming that channel using Niv once again.

Other possibilities

The sky is really the limit with channels. You can use them to offer any Nix expressions you want, and you are not at all limited to the simple use cases I’ve shown here. Among the many possibilities:

  • Offer a central library of functions, packages, etc. for your specific needs. If your org uses a lot of Python and Rust, for example, you can create a channel that offers helpers for those languages while also pinning everything to a specific version of nixpkgs, thus ensuring reproducibility across all “clients” utilizing the channel. This might include a pinned Rust via a central rust-toolchain file, a pinned JDK, pinned npm dependencies, and so on.
  • Offer nixpkgs with certain denylisted packages removed in case you have special security or other concerns.
  • Offer purely custom packages, functions, etc. that have nothing to do with nixpkgs.
  • Create different channels for use by different operating systems (e.g. *-linux vs. *-macos) rather than using if/then logic inside of Nix expressions to support different platforms.
  • As mentioned in the note above, use different Git branches to provide different channels from the same repo, e.g. staging, production, sandbox, etc.