If you followed one of my earlier posts about NixOS, then you should already be somewhat familiar with the basics of managing and configuring your NixOS.
However, Nix can be much more than a single configuration.nix file? In this guide we will be setting up not one, BUT TWO powerful tools. Flakes and Home Manager!
Ok, but what are Nix Flakes?
Flakes are an experimental feature of Nix that aims to fix some of the challenges around true reproducibility. They ensure that the same inputs always produce the same outputs.
You can manage your system configurations (yes, plural), distribute nixpkgs overlays, and modules more efficiently. Flakes make it easy to share your entire system down to the exact package version with anyone.
Flakes are stored in a file called flake.nix
.
{ | |
description = "A simple NixOS flake"; | |
inputs = { | |
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; | |
}; | |
outputs = { self, nixpkgs, ... }: { | |
nixosConfigurations = { | |
hostName = nixpkgs.lib.nixosSystem { | |
system = "x86_64-linux"; | |
modules = [ ./configuration.nix ]; | |
}; | |
}; | |
}; | |
} |
Above is one of the simplest flakes you can make for managing your home system.
The inputs
section of the file specifies the flake’s dependencies. Here we have nixpkgs
defined. It points to the GitHub URL that contains the unstable branch of nixpkgs. You can look at it here.
The second main part is the outputs
. It inherits itself and nixpkgs
from the inputs
section above. The single output in this case is nixosConfigurations
. As it sounds, this output defines the NixOS system configs.
hostName
is a placeholder variable for the machine’s name. In most cases, it is named after the machine's hostname (which you can find by typing hostname
in the terminal). You can call it whatever, but if it’s not the machine’s hostname then you will have to call that name specifically when building.
It calls the nixpkgs.lib.nixosSystem
function. That function specifies the system’s architecture and the modules list. As you might have noticed, the modules list contains the relative path to the system configuration file.
When the flake file is built, it creates a flake.lock
file. Just like how Javascript has a package.json
file and a package-lock.json
file, Nix has a flake.nix
file and flake.lock
file.
Before we move on to enabling and creating a flake file, let’s touch on Home Manager and how we will be using it.
What is Home Manager?
While /etc/nixos/configuration.nix
configures the system, Home Manager takes it a step further and allows you to manage user environments. With the Nix language, you can declaratively define user environmental variables, packages, and most importantly, dotfiles.
It can be used as standalone software, allowing users to update their dotfiles without root privileges, or as a NixOS module. If it’s a Nix module then you will have to rebuild your system every time you want to update your dotfiles. I personally have it set as a module, but you can install it standalone as well.
Getting started
To get started you first need to enable flake support. It is technically an experimental feature, but this is because it’s subject to breaking changes in the future. As of right now it is really stable and is becoming standard for most configurations.
Adding flakes is super simple. Copy this line and add it to your configuration.nix
file.
experimental-features = [ "nix-command" "flakes" ];
Rebuild your system and then you can start writing flakes.
In the root of your configuration directory /etc/nixos
run the command:
nix flake init
This will generate an extremely basic flake.nix file. Go ahead and change the default description to whatever you want and then remove everything from inside the outputs
object.
You can copy the flake example from before for you starting flake. To build this flake you will need to run this command from the directory the flake is in.
sudo nixos-rebuild switch --flake .#
The --flake
flag points to the flake we are switching to. The period is the directory that the flake is stored in and the number sign specifies the hostname defined in the file. If you called the hostname in the flake the same as the machine host name then you can leave the symbol blank, otherwise, you will need to append the hostname defined in the flake to it. Like this .#hostName
.
If everything went well then you should have a flake.lock
file. If you want, open it up and look around. It will have a bunch of hashes and other stuff. Don’t edit it.
To install home manager go ahead and use this flake file. It will install home manager as a module.
{ | |
description = "NixOS configuration with Home Manager"; | |
inputs = { | |
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; | |
home-manager = { | |
url = "github:nix-community/home-manager"; | |
inputs.nixpkgs.follows = "nixpkgs"; | |
}; | |
}; | |
outputs = inputs@{ nixpkgs, home-manager, ... }: { | |
nixosConfigurations = { | |
hostName = nixpkgs.lib.nixosSystem { | |
system = "x86_64-linux"; | |
modules = [ | |
./configuration.nix | |
home-manager.nixosModules.home-manager | |
{ | |
home-manager.useGlobalPkgs = true; | |
home-manager.useUserPackages = true; | |
home-manager.users.<yourUsername> = import ./home.nix; | |
# Optionally, use home-manager.extraSpecialArgs to pass arguments to home.nix | |
} | |
]; | |
}; | |
}; | |
}; | |
} |
Make sure to replace <yourUsername>
with your actual username. In the inputs, you might notice that home manager is split into two parts. The GitHub URL points to the master branch. The reason for this is because it follows the unstable branch of nixpkgs. If you are using a stable release of Nix, update the home manager URL accordingly. The line below the URL tells home manager to follow the unstable branch.
Configuring things inside Home Manager
After rebuilding (using the command from before), you can start configuring the ./home.nix
file. You can also define the path elsewhere, just make sure it’s relative to the flake.
Here is an example file. You can install user specific packages and define the dotfile for zsh.
{ config, pkgs, ...}: | |
{ | |
# if you config gets too long, split it up into smaller modules | |
imports = [ | |
./git # looks for ./git/defualt.nix | |
./hypr/hyprland.nix # looks for ./hypr/hyprland.nix | |
]; | |
# The User and Path it manages | |
home.username = "<yourUsername>"; | |
home.homeDirectory = "/home/<yourUsername>"; | |
# Let Home Manager manage itself | |
programs.home-manager.enable = true; | |
# List of user programs | |
home.packages = with pkgs; [ | |
zsh | |
git | |
firefox | |
]; | |
# ZSH | |
programs.zsh = { | |
enable = true; | |
enableCompletion = true; | |
enableAutosuggestions = true | |
shellAliases = { | |
_ = "sudo"; | |
h = "history"; | |
hg = "history | grep "; | |
}; | |
}; | |
} |
There is a ton more you can do with Home Manager and Flakes too, but there is too much to cover in this one post. Here are some resources that you can use to learn more about them!