Note: Since this article was written, there have been changes to how Home Manager works with flakes. Additionally, while flakes are still marked as an experimental feature, the stable version of Nix has also since advanced. Newer versions of Nix and Home Manager have also changed where profiles are stored by default (there may now be a
While a lot of the content of this article still broadly applies, you may wish to consult the Home Manager manual (specifically the chapter on flake use), or the Nix Reference Manual, for more up to date information.
If you are a Nix user, you may have heard of Home Manager as the recommended way to manage your user environment. You may have also heard of flakes, the upcoming new way of managing dependencies and packages.
You can combine the two, and use Home Manager with flakes. Frequently, this is done by including a Home Manager configuration in the (also flakified) system configuration. Home Manager can, however, be also used on operating systems other than NixOS, in which case the NixOS system configuration is not there. Fortunately, Home Manager can also use flakes entirely on user level. Let's assume, then, that we have an existing Home Manager installation, not currently based on flakes, and want to switch it over to flakes.
Okay, but first, what are all those things?
With your usual Nix installation, each user can manage their environment with the
nix-env tool. When the user installs or uninstalls things with
nix-env, a new generation of the user's environment is created with these changes, providing a simple way of managing packages that resembles the classic package managers found on other operating systems. The
nix-env approach does have some problems, however, broadly relating to it being imperative in a system focused on being declarative. To address these problems Home Manager was created. With Home Manager, the user can write a configuration for their environment, similar to a NixOS system configuration. This Home Manager configuration specifies the packages and home directory configuration of a single user.
Flakes, on the other hand, are an upcoming feature of Nix itself. Flakes are intended to introduce a new way of declaring dependencies in Nix packages, improving reproducibility and, from a practical standpoint, proving a better alternative to the current approach of using channels and ad-hoc pinned dependencies. If you have ever used Niv, you can think of flakes as that, but better integrated into the Nix tool itself.
The experimental Nix
Flakes are a feature of the upcoming 2.4 version of Nix. The standard Nix, even in the unstable Nixpkgs channels, is Nix 2.3, but there is also
pkgs.nixUnstable, which points to 2.4 builds.
nixUnstable come with a binary called
nix, and so collide. If we want to experiment with flaky Nix without overriding stable Nix, we can make a wrapper (courtesy of the NixOS Wiki). An easy way to do this is to use our existing
home.nix, the Home Manager configuration file:
Note that even with Nix 2.4, we need to have the flakes experimental feature explicitly enabled. This can be done by adding
experimental-features = nix-command flakes to our
nix.conf (generally found in
~/.config/nix), but if we do this, regular Nix 2.3 will complain about not knowing what
experimental-features is. As an alternative, we apply these settings on the command line in the wrapper.
After switching to our new configuration we will have a
nixFlakes in our
PATH, and so will be able to use it from the command line.
Let us assume that our prior Home Manager configuration was kept in a Git repository somewhere in our home directory, with
~/.config/nixpkgs/home.nix symlinked to a relevant file within the repository.
To create a basic
flake.nix, we can use execute
nixFlakes flake init within our configuration repo. This command can be used to create flakes out of templates, and if we don't explicitly ask for a specific template, it uses the default one, which looks something like this:
As we can see, a flake file is simply an attrset. The two important attributes in it are
inputs (absent from template), and
inputs defines the dependencies of our flake, and
outputs defines the things our flake offers, that other flakes and tools can consume.
The template does not have explicit
nixpkgs is resolved using our Nix flake registry (a locally stored map from flake names to flake URLs, that Nix populates from a global registry on the Internet). The template does have two outputs:
packages, which defines one named package, and
defaultPackage, which marks the one package as the flake's default.
Flakes can define various outputs, which are then used by various tools—
defaultPackage, for instance, is what Nix would look at if we asked it to install this flake into our profile, without explicitly asking for any specific package from inside the flake. Home Manager, on the other hand, uses the
A basic Home Manager configuration
homeConfigurations should be an attrset, mapping names to Home Manager configurations. The names are either usernames, or in the format of "username@hostname".
For inputs to our flake, we will want to include Home Manager. This does mean that our flake is technically independent of our current Home Manager installation. In fact, we could bootstrap Home Manager this way without having Home Manager installed in the first place! Having Home Manager already installed is also okay—our prior Home Manager generations will not be reset.
Assuming our username is
someuser and our hostname
somecomputer, we can come up with a basic flake like this:
As we can see,
"someuser@somecomputer" is mapped to a call to
homeManager.lib.homeManagerConfiguration. We are calling a function exported by Home Manager's flake that will create a Home Manager configuration, out of the attrset that we pass to it.
configuration should be the most familiar bit of said attrset. This is essentially the same function as the one we would normally define in our
home.nix. In fact, instead of defining our configuration inline, in the flake, we could do
configuration = import ./home.nix;, which is the practical way to move our old configuration to flakes. The one caveat is that some of the
home settings that might have previously been in our
home.nix file are now in the flake—
home.homeDirectory now live adjacent to, instead of in
Switching to our home-manager configuration
We can build our home-manager profile with just our flakey Nix. This can be done by building the
activationPackage attribute of a particular configuration. In this specific case, this means invoking
nixFlakes build '.#homeConfigurations."someuser@somecomputer".activationPackage'. After the build completes and we have our result folder, switching to the newly built configuration is simple: invoke
result/activate from the command line.
We should now be in our new profile. It is, of course, possible something has gone terribly wrong and our new profile is broken in some way. Fortunately, we are using Nix, so we can perform a rollback. Invoking
home-manager generations should give us a list of generations, and paths to them. We can simply take the next-to-last path, append
activate it to it, and run that, which should reactivate our previous generation, rolling us back. If
home-manager is absent from our
PATH, we can also go to
/nix/var/nix/profiles/per-user/someuser, where we can find a number of
home-manager symbolic links. Again, if we follow the next-to-last link, we can use the
activate script within to restore that generation.
home-manager tool with flakes
We could just repeat the
nix build command whenever we want to switch to a new Home Manager generation, but that is a bit awkward. Fortunately, the
home-manager tool, besides working with the usual
~/.config/nixpkgs/home.nix, can also work with
Unfortunately, in this situation, we cannot simply create a symlink from
~/.config/nixpkgs/flake.nix to wherever our configuration repository is, as we would experience problems due to restricted mode; Nix will refuse to poke around the symlink target unless
--impure is passed. One interesting—if seemingly hacky—thing we could do, however, is create another flake.
Yes, we can use local filesystem paths as flake inputs. Of note is the fact that we could use network URLs here as well, and let Nix handle pulling our configuration from elsewhere, if we happen to have it on an easily-reachable server.
We have also declared
nixpkgs as a separate input, and pinned
nixpkgs input to our
nixpkgs input. This is one way to achieve a workflow similar to that we would have previously used with channels: our
nixpkgs input will stay pinned where it is until it is updated. This can be useful if, say, we are changing something in our home-manager configuration and want to rebuild without necessarily downloading a lot of fresh updates.
We can update individual inputs by using
nix flake lock --update-input nixpkgs ~/.config/nixpkgs.
home-manager switch and
home-manager build should work with the configuration specified in
~/.config/nixpkgs/flake.nix, although we might have to make unstable Nix our main
nix binary, rather than have it behind
If we do not add a
flake.nix to our
~/.config/nixpkgs, we can instead explicitly point
home-manager at a flake:
home-manager switch --flake path:/home/someuser/stuff/home-manager-config.
One of the reasons to get into flakes is for their ability to manage inputs other than mainline Nixpkgs. With stable Nix, the options here are usually either manually managing
fetch* functions that pull in some repository, or adding Niv, and letting it manage pinning the added packages. With flakes, however, this is handled natively by Nix itself.
There are two types of repositories we can encounter in the wild: ones which have their own
flake.nix file, and ones which do not. Fortunately, Nix can handle even the non-flake inputs—we simply have to indicate that the input is a non-flake:
The main thing to point out here is
extraSpecialArgs—this is how we pass extra arguments to the
configuration function, outside of the
pkgs which Home Manager will supply by itself. Inside
configuration itself, we import
nixGL just like we would if we had used Niv or called
fetchFromGitHub ourselves. The details of how to import a given non-flake input, and what to do with it, will vary, but generally non-flake instructions should be adaptable to use with flakes.
Flakes, on the other hand, are more structured. Conventional flakes will generally provide
overlay as outputs. As such, we will have two options for installing packages: either by grabbing the package from the two packages outputs directly, or by applying the flake's overlay onto our imported Nixpkgs.
The former case is pretty simple:
Doing the same with an overlay is slightly more complicated:
While we could use
nixpkgs.overlay inside the Home Manager
configuration function, we can also apply the overlay in the flake, by defining the
pkgs attribute (in fact, we can do both at the same time). The only caveat is that we will also need to supply the
system when importing
nixpkgs in the flake.
The deploy-rs tool being under
pkgs.deploy-rs.deploy-rs is a consequence of the deploy-rs overlay being structured like that, and not a specific quirk of flake overlay use.
Hopefully these examples give a broad overview of how Nix flakes can be used in practice, but to augment them, here is some extra stuff to read:
- "Nix Flakes, Part 1: An introduction and tutorial" – Eelco Dolstra's introduction to the general concept, and the whys and hows of Nix flakes
- "Flakes", from NixOS wiki – a more practical reference to flakes
- Nix manual, unstable version – since flakes are a feature of Nix itself, the unstable Nix manual documents their use
- Home Manager Manual – while documentation on flake use in Home Manager is not extensive at the moment, it is a useful reference
- Home Manager pull requests #1697 (rendered) and #2009 (rendered) – as of writing unmerged documentation on flake use with Home Manager