Baby steps with NixOS

Seán Murphy
5 min readOct 10, 2022

For some years, the smartest guys in my environment have been extolling the virtues of nix generally and NixOS in particular; it supports rapid creation of stable, reproducible environments Which Just Work. The principle was interesting and clearly it was a problem worth solving but I never investigated further than reading a couple of blog posts about it. More recently, I’ve encountered more people using it, so I decided to look deeper.

Penguins with go fast stripes

One obvious initial hurdle for anyone who is taking their first steps with nix is that there are different components and it’s not immediately obvious how they all relate: there is the nix language, the nix package manager, there is NixOS, there is nix shell and there is home-manager for nix deployments in a user account. I still don’t fully grok all this, so I can’t provide a clear explanation here.

My particular use case is to have a dedicated command line oriented Virtual Machine with a set of tools I typically use for work: I wanted to set up a dedicated NixOS instance which can be rebuilt quickly and easily and runs as a VM on my local machine. I also liked the notion that it can easily be set up on a cloud platform with the same configuration or on other machines which I might be using should I need to change laptop or want to use my desktop for some reason.

For me, solutions which integrated with Ubuntu — my standard Linux distro — in which both nix and Ubuntu wanted to perform package management were likely to lead to some conflicts and having a clear separation in which graphical oriented tasks take place in Ubuntu and CLI work takes place under NixOS could work: if it’s good enough for Mitchell Hashimoto, it’s definitely good enough for me ;-)

It took me a little while to understand the deployment approach. Here’s the high level view:

  • Boot from a NixOS iso in a virtual environment; this gives a shell which is accessible via a console and can be accessed via ssh if a password is created. Note that nothing is written to disk at this stage — it’s similar to the live boot option of many Linux distros;
  • From this shell context, disk configuration in the virtual environment can be performed. Naturally, this is environment dependent but typically takes the form of using parted to create labelled partitions with the labels indicating which partitions should be used for a persistent NixOS installation. Then it’s necessary to format the disk(s) and mount;
  • Before installing NixOS, it’s necessary to run nixos-generate-config with appropriate parameters: this creates a default nix configuration and some hardware specific configuration (id of disk on which OS is installed, kernel modules to be installed etc). At OS installation, these will be written to /etc/nixos/configuration.nix and /etc/nixos/hardware-configuration.nix respectively;
  • Once the configuration has been generated, it’s now possible to install NixOS. Run nixos-install which will create a bootable OS on the partitioned disk. Run reboot to shut down the live boot session and boot from your shiny new NixOS installation.

That’s the higher level view: the details are in the NixOS documentation, so there is no point repeating them here.

This, of course, is only the first step: it generates a bootable operating system with only a root user. To really see the power of nix, it is necessary to start modifying the configuration.

When modifying NixOS configuration, the basic workflow is to make the changes and run nixos-rebuild switch — this compiles the updated configuration and, if valid, applies it. Usually this is not problematic but in case there is an error during the update, rollback is done using nixos-rebuild switch --rollback: this reverts to the previous state. You will probably encounter errors in the configuration which do not get applied as the configuration cannot be compiled — in this case an error is generated at the problematic line number in the configuration file. That’s the overview: let’s see some examples…

The standard configuration does not install ssh daemon: to install an ssh daemon, it’s necessary to add the following clause to the configuration and run nixos-rebuild switch:

  # Enable the OpenSSH daemon.
services.openssh = {
enable = true;
passwordAuthentication = true;
};

It is then possible to access the ssh server on the VM; however, as the only account is currently root and ssh root logins are disabled by default, it’s necessary to set up a standard user and a password — you can use the standard Linux tools useraddand passwd to create the new user. Once this has been done, ssh login as this user is possible.

It’s very straightforward to add new packages to the standard configuration eg go, vim, curl, jq, fish and many others: for these standard tools, it is straightforward to guess the name of the required package when at the initial experimentation stage. Running nixos-rebuild switch applies the updated configuration with the new packages and, if successful, you can run them directly from the terminal. If you don’t know the name of a package, it’s possible to perform a search against the package repository. In my case, I tried the nix search command but this is somehow evolving and requires some further parameters: I ended up using the not so svelte

nix --extra-experimental-features nix-command --extra-experimental-features flakes search nixpkgs fzf

to search for packages containing fzf. I guess there are better ways to do this.

The next thing I did was to modify my user configuration slightly, giving sudo access, setting fish as a default shell and adding an ssh public key for login. Adding the snippet below to the configuration.nix and applying it gave the desired result. It was really nice to be able to control these disparate capabilities from a single place as they are usually distributed between different configuration files.

  users.users.sean = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
shell = pkgs.fish;
openssh.authorizedKeys.keys = [
"<INSERT PUBLIC KEY>"
];
};

I’ve only taken a few basic steps with NixOS so far but I’ve been very impressed with how easy it is to make changes to the set of installed packages and the fact that these are all driven from a single configuration is very compelling — although apt is a great package manager (as are others), this approach is definitely feels much simpler and more intuitive. Also, a significant benefit of the nix approach seems to be the mechanism used to specify basic configurations for many packages: this does not need to be done inside some specific file in /etc post installation, but is part of the nix configuration itself.

Clearly NixOS is not a panacea — configuration management is a complex problem and I’m very interested to explore the potential and also to understand the limitations.

Next up, I’ll be going through how I managed to set up Yubikey, GPG and ssh in this context.

Acknowledgement: Thanks to Dario Wirtz for giving me feedback on this!

--

--

Seán Murphy

Tech Tinkerer, Curious Thinker(er). Lost Leprechaun. Always trying to improve.