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 usingif
/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.