Switching non-NixOS Home Manager to flakes
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 ~/.local/state/nix
).
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.
Both nix
and 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.
Starting a flake.nix
file
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 outputs
. 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 inputs
; 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 homeConfigurations
output.
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.stateVersion
, home.username
, and home.homeDirectory
now live adjacent to, instead of in configuration
.
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.
Using the 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 ~/.config/nixpkgs/flake.nix
.
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 homeManagerConfig
's 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 --update-input
: 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 nixFlakes
.
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
.
Non-Nixpkgs packages
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 fetchTarball
or 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 packages
, defaultPackage
, and 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.
Further reading
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