Intro to Nix Channels and Reproducible NixOS Environment
This introduction assumes you have played with NixOS a bit, you know about content addressability and why it is important, and how Git repositories represent a distributed content addressed storage system.
Git and Github is used as the source control for all of NixOS and NixPkgs. Both NixOS and NixPkgs source code is located here: https://github.com/NixOS/nixpkgs. This means every package that is available via Nix is defined in that repository. This includes OS services and general software. Nix, the language interpreter and the package manager tool is however located here: https://github.com/NixOS/nix.
Channels is a rolling distribution endpoint that provides the latest builds of NixOS and Nixpkgs, and the Nix expression set corresponding to those builds. Because NixOS is just a particular build of NixPkgs that is geared towards running a full OS, we'll be using NixOS channels and not NixPkgs channels. The channels have names of the form:
nixos-YY.MM
nixos-YY.MM-small
nixos-unstable
nixos-unstable-small
nixpkgs-unstable
We will ignore NixPkgs channels (nixpkgs-*
) and only focus on NixOS channels (nixos-*
) because the NixPkgs channels
are intended to be used by non-NixOS users like other Linux distributions or Macintosh users. The exact artifacts that
the channel points to will have their names detailed in this way: label-YY.MM.N.H-*
. The label
is is name of the
artifact such as nixos
, nixos-graphical
, nixos-minimal
. The N
is the iteration number of the major
version YY.MM
. The H
is a 7 character truncated Git hash. The *
is the reamining metadata for the build artifact.
Channels are built from Git release branches on the official NixPkgs source
repository: https://github.com/NixOS/nixpkgs. The branches are notated
as release-YY-MM
. On every commit to these branches, Hydra (the Nix build system) clones the source code and builds
them while running tests. If these tests succeed, the successful branch commit is then propagated to a channels
repository located at: https://github.com/NixOS/nixpkgs-channels. Hydra
will also "release" them to the official channel endpoint at: https://nixos.org/channels/
. Once a commit is available at https://nixos.org/channels, it is guaranteed that the
packages specified inside the corresponding Nix expressions will be built, and cached in the binary cache.
Let's see if these things match up (here we use httpie
, pup
and jq
):
http https://nixos.org/channels/ | pup 'table tr:nth-child(n+4) td a attr{href}'
http 'https://api.github.com/repos/NixOS/nixpkgs/branches?per_page=100' | jq '[.[] | select(.name | startswith("release"))]'
http 'https://api.github.com/repos/NixOS/nixpkgs-channels/branches?per_page=100'
The released channel hashes (https://nixos.org/channels/) exactly match the channels repository branch hashes (https://github.com/NixOS/nixpkgs-channels).
However because Hydra takes time to build and verify release branches, the release branches at https://github.com/NixOS/nixpkgs will be sometimes ahead of the released channel branches and released channels.
Some extra things to note:
- The
small
channel variants are updated before the entire package set is built. This means thesmall
channel variants will be up to date with the latest security changes and bug fixes compared to the non-small
variants. This does mean that sometimes Nix will force a build from source rather than downloading pre-built packages. - The
nixos-unstable
channel corresponds to themaster
branch at https://github.com/NixOS/nixpkgs. The master branch will sometimes be ahead of the channel, as the channel relies on Hydra to finish evaluation. - There is no channel that corresponds to the
staging
branch. - The
staging
branch at https://github.com/NixOS/nixpkgs is even more unstable thanmaster
. Thestaging
branch is intended for changes that will cause mass-rebuilds of packages, and is supposed to be merged back intomaster
every couple weeks.
So which source should we source our channels from? It depends:
- If you are developing on NixOS, you'll often need to make use of the latest commits or pull requests, in that case, you have to use branches from https://github.com/NixOS/nixpkgs.
- If you're just using NixOS and want to have a stable experience, you should use the official released channels endpoint at https://nixos.org/channels/
- If you're power user, but you still want a stable experience, you should use the Git release branches at https://github.com/NixOS/nixpkgs-channels.
The great thing about Nix, is that you can mix-and-match. Your system can be sourced from release branches, while cherry-picking changes directly from official nixpkgs repository, and even picking up forks, random commits and making use of them inside a Nix profile or a nix-shell.
However if you need to download the built ISOs for a given release, these should be downloaded from https://nixos.org/channels/, because the repositories don't host these artifacts. Another source for built artifacts is Hydra itself, but we'll not go into this yet.
It is at this point where we must differentiate the channels
at https://nixos.org/channels/ and the Git repository sources from which those channels
are built from. The minimal definition of a "channel" is a directory with 2 files: nixexprs.tar.xz
and binary-cache-url
. The nixexprs.tar.xz
is the tarball that contains a default.nix
which is what is actually
used to evaluate Nix expressions. The binary-cache-url
is just a text pointer to another location containing the cache
for the Nix expressions.
The main utility of using these official channels over the sources in the Git repositories is so that you can know when
the binary cache is ready to supply all the packaeges in the channel's Nix expression. Nix provides a command
called nix-channel
which allows you manage system and profile channels. The system channel is also the root user's
channel.
Ultimately you can think of using Nix channels for every day usage, but you should be using the Git repository hashes as sources for your package sets when you care about reproducibility, which basically whenever you're developing software. On installation of a fresh NixOS system, there will be one channel the system is already subscribed to.
Running sudo nix-channel --list
will show something like: nixos https://nixos.org/channels/nixos-17.03
. The nixos
name is just an alias, it allows you refer to multiple channels during package installation. (This is not as flexible as
directly using the Git sources, because you need a proper channel structure including the binary-cache-url
, and we
don't have a valid binary cache for every single source commit hash.)
Having multiple channel aliases, allows you perform installations like nix-env --install --attr nixos.packagename
or nix-env --install --attr mycoolchannel.packagename
. This also means anybody can publish a channel, they just need
to provide a binary cache as well.
We had to use sudo
because by default in order to see the channel of the root user/system. But every user on a NixOS
system can manage their own set of channels, where the channel aliases can override the system channel aliases.
# make sure you don't have `.` or `-` in the channel name!!!
# this is a bug: https://github.com/NixOS/nix/issues/1457
nix-channel --add https://nixos.org/channels/nixos-17.03-small nixosSmall1703
nix-channel --update nixosSmall1703
nix-env -iA nixosSmall1703.xmlstarlet
Adding a channel by itself does not download the channel expressions, you need explicitly run the update command. The
channels are stored in /nix/store
but referenced from ~/.nix-defexpr
(this exists for both normal users and the root
user). If you want to learn more about this, watch the contents of ~/.nix-defexpr/channels/
and ~/.nix-defexpr/channels_root/
while running the above commands.
The nix-channel
command is quite limited, it does not tell you what channels are out of date, so you would need to run
the --update
command often to get the latest released iterations of each channel. You can find the exact version of
your channel by looking at cat ~/.nix-defexpr/channels/nixosSmall1703/svn-revision
or ~/.nix-defexprs/channels_root/nixos/svn-revision
. But this is not an official supported technique.
You can remove channels with nix-channel --remove nixosSmall1703
, and you don't need to run nix-channel --update
.
Because of how channels work, we prefer to side-step this functionality and instead pin our NixOS system a particular commit hash within a released channel. This is what makes a NixOS system reproducible. We'll try to make sure that the packages we want to install are all from a single commit hash. Note that this is sometimes not possible when a package at a particular iteration or version is only available at a different commit hash, and you don't want to move your entire system to that commit hash. However with Nix, it's very easy to have packages from multiple different commit hashes cohabiting on the same system, and when you need isolation, this can be provided via Nix profiles or nix-shell.
While we could just import the desired Nix expression inside our configuration.nix, certain tools like nix-env
relies
on the ambient channel setup to install packages. The right way to solve this would be to make nix-channel
support
specific commit hashes and local directories containing Nix expressions, rather than only channel directories. This
would make pinning a NixOS system much easier, and would seamlessly work with the existing nix-env
expectations. There
is however a workaround involving the utilisation of the Git content-addressed system and NIX_PATH
. This workaround
was discovered
here: http://anderspapitto.com/posts/2015-11-01-nixos-with-local-nixpkgs-checkout.html (
There is an error with this workaround that is discussed below.)
# switch to super user
sudo --login
# we're going to create /nix/nixpkgs as our pinned nix expressions
rm --recursive --force /nix/nixpkgs
git clone https://github.com/nixos/nixpkgs /nix/nixpkgs
cd /nix/nixpkgs
git remote add channels https://github.com/nixos/nixpkgs-channels
git fetch --all
git checkout -B channels-nixos-17.03 channels/nixos-17.03
# /nix/nixpkgs will now be set to branch channels-nixos-17.03 and tracking channels/nixos-17.03
Now go into your configuration.nix, and
set nix.nixPath = [ "nixpkgs=/nix/nixpkgs" "nixos-config=/etc/nixos/configuration.nix" ];
.
Since the current NIX_PATH
hasn't updated yet, we need to force a rebuild with the the new source for Nix
expressions: nixos-rebuild -I nixpkgs=/nix/nixpkgs switch
.
When you want to update you can just perform a git pull
on the directory. These Git commands will also be helpful when
you want to checkout a specific commit hash, or when you want to switch to a different channel source.
# show remote info
git remote show channels
# update all remotes
git fetch --all
# see all the branches that is available
git branch --all --verbose --verbose
# see what the remote provides along with pull requests to that remote
git ls-remote channels
# if you have added changes to your local branch, you can rebase your changes on top of any updates
git rebase channels/nixos-17.03 channels-nixos-17.03
# if you want to reset to upstream channel and drop any local changes (resets the working tree as well)
git reset --hard channels/nixos-17.03
Now we can remove the system channel and any user-channels as they will no longer be used:
nix-channel --remove nixosSmall1703
sudo nix-channel --remove nixos
All nix commands except the nix-env
command will work. The nix-env
currently only uses ~/.nix-defexpr
. This
issue (https://github.com/NixOS/nix/issues/993) asked to make nix-env
use NIX_PATH
or <nixpkgs>
as a fallback if there are no channels installed. However this was only implemented in the
new Nix UI project, and is not available on the current nix-env
(1.11.15). The only way to make nix-env
use NIX_PATH
is to alias it with the option --file '<nixpkgs>'
. This is safe as you can override the --file
option
by specifying it again. Once you do this, attribute path installation doesn't require a channel name. For
example: nix-env --file '<nixpkgs> --install --attr 'hello'
.
While this makes sense for general purpose use, for unattended NixOS servers where it doesn't make sense to
use nix-env
, there is no need to do all of this, and you can just directly import a content-addressed Nix expression
set inside your configuration.nix.
At this point we can see how to acquire packages from different sources (including other commit hashes) and have them
all coexist. There are 2 ways to do this. Through the imperative nix-env
command that mutates your profile state, and
through Nix expressions that you can inline into your configuration.nix or your nix-shell configuration files.
nix-env --file '<nixpkgs>'
nix-env --file '/package.nix'
nix-env --file '/directory/containing/default.nix/'
nix-env --file '/tarball/containing/default.nix/tarball.tar'
nix-env --file 'https://github.com/NixOS/nixpkgs/archive/master.tar.gz'
nix-env --file 'https://github.com/NixOS/nixpkgs/archive/74f22ff827d7c91fe26a62c69a53556a62ecc70c.tar.gz'
nix-env --file 'https://nixos.org/channels/nixos-unstable/nixexprs.tar.xz'
Because /nix/nixpkgs
is owned by root, if you run Git operations via sudo
, you'll end up with permissions that don't
allow usage by other users (due to the default umask used by Git when creating new files). You'll need to run these
commands to reset to proper permissions. We need to make sure that all directories are at least can be readable and
executable by all users, and that all files are at least readable by all users.
they are just build support files
sudo chmod --recursive u=rwX,g=,g=rX,o=,o=rX /nix/nixpkgs
There may be a way to avoid these permission problems in the future.
As you can see we rely on Github to expose specific commit hashes as tarballs that our nix-env
can consume. This makes
it convenient to install packages from different branches, forks and even pull requests.
The same thing can be done inside a Nix expression such as our configuration.nix. The key primop is import
. This
command expects either a directory containing default.nix
or a Nix file. If we intend to acquire an expression set
remotely, we just need to use fetchTarball
to expose the Nix expression. Note that nixpkgs
based expressions is
exported as a function, which means you can apply an empty attribute set to it before using it. The attribute set
argument is intended to provide various flags into the Nixpkgs attribute set.
let
pkgs-env = import <nixpkgs> {};
pkgs-my = import ./package.nix;
pkgs-dir = import ./packages/containing/default.nix/;
pkgs-unstable = import (fetchTarball http://nixos.org/channels/nixos-unstable/nixexprs.tar.xz) {};
in
{
environment.systemPackages = [
pkgs-env.packageAttrName
pkgs-my.packageAttrName
pkgs-dir.packageAttrName
pkgs-unstable.packageAttrName
];
}
Any path specified with angle brackets such as <nixpkgs>
utilises the NIX_PATH
environment variable to find files.
We just set our NIX_PATH
to nixpkgs=/nix/nixpkgs
, which means <nixpkgs>
just resolves to /nix/nixpkgs
. However
if we instead had <something/subthing>
, this would try to look for something/subthing
in each of the paths listed in
the NIX_PATH
. This is how it works:
NIX_PATH = nixpkgs=/nix/nixpkgs
<nixpkgs> = /nix/nixpkgs
NIX_PATH = nixpkgs=/nix/nixpkgs
<nixpkgs/lib> = /nix/nixpkgs/lib
NIX_PATH = nixpkgs=/nix/nixpkgs
<nonexistent> = Error
NIX_PATH = /somedir/containing/something/:nixpkgs=/nix/nixpkgs
<something> = /somedir/containing/something/something
You can find out ahead of time whether Nix will find an angular path by
using: nix-instantiate --eval --expr '<something>'
.
The same techniques can be further applied in your shell.nix when developing inside an isolated Nix shell, you should make sure your software environment is reproducible.
However if you are developing a package for the purpose of inclusion into official Nixpkgs, after you've done with
development, if you are dependent on packages that is already available in one of the Nixpkgs iterations, you should
rely on the ambient Nixpkgs provided through the pkgs
attribute. This ensures that packages within a single Nixpkgs
package set work in harmony with each other, and reduce the number of unnecessary duplicated dependencies. We have found
that within the Nixpkgs source, there are times when multiple versions of a particular package is required by different
downstream packages, this is when the particular iteration of Nixpkgs will often contain multiple versions of the same
package. However, as soon as these conflicts are fixed, either by the upstream of the Nixpkgs maintainers, the older
versions of the package will be deprecated and removed from future iterations of NixPkgs.
One possible downside is that you won't get automatic security updates with this approach. However this depends on your priorities. When in the act of developing software, reproducibility matters more. When in the act of maintaining/operating software, automatic security updates matter more. You can get the best of both worlds by building a system to monitor security issues relevant to your software dependency graph, and trigger updates then builds then tests before finally deploying.
UPDATE: We have found a way to avoid needing to apply permission changes after updating your /nix/nixpkgs
. You do
this by applying a temporary umask
change. The reason is that the permission problems applies to new files being
created by git
.
we use a subshell to make umask change temporary
(umask 022 && sudo git -C /nix/nixpkgs checkout channels-nixos-17.09) (umask 022 && sudo git -C /nix/nixpkgs pull)
The above can be automated by changing sudo
umask
options: https://unix.stackexchange.com/a/278817/56970
Also, when you are using sudo
while doing git
operations requiring authentication. You may require sudo -E
or sudo --preserve-env
to preserve your SSH or GPG environment.