Explore NixOS: A dive into the world of declarative Linux

Keir Williams
Version 1
Published in
7 min readSep 13, 2023
NixOS logo by Tim Cuthbertson is licensed under CC BY 4.0

The traditional way of managing Linux deployments is usually the manual editing of configuration files and bootstrap scripts. Some teams have also adopted more modern methods such as Ansible. However, all these systems feel like you’re working up against something. They feel like workarounds.

Nix: the package manager

Nix is a package manager ecosystem first introduced in 2003. Software is placed in unique directories via cryptographic hashes. These hashes are generated from the build inputs, dependencies, the various metadata and scripts. What this means is Nix packages are deterministic. Given the same inputs mentioned previously, they will always produce the same output with the same hash. It is consistent and predictable.

Nix packages are defined through a domain-specific language (DSL) which shares the same name, Nix. The Nix language is a lazy, functional programming language, with an emphasis on purity.

The majority of available packages for Nix are distributed via the nixpkgs repository. At the time of writing, Repology reports that nixpkgs has more than 85,000 packages, compared to Ubuntu with 35,000. One of the largest repositories, with a sizeable amount, if not all, available as binaries.

NixOS: the Linux distribution

NixOS is a Linux distribution based on the Nix package manager. With declarative configuration at its core, it takes the Nix ecosystem further. Nearly the entire file system hierarchy is produced from Nix derivations. The core of this distribution is a Nix file that declares users, groups, services, packages, networking, and various others configurations: /etc/nixos/configuration.nix

How does NixOS benefit DevOps?

NixOS fits very nicely in the DevOps sphere. In fact, the NixOS project is very friendly to DevOps, with many sub-projects geared towards DevOps uses. Let’s cover a few aspects that lend well to DevOps.

Declarative configuration

At the core of NixOS is a configuration file, written in the Nix language. This Nix expression is used to specify packages, users, groups, services, networking, and a whole set of other things typically configured on a Linux system.

Due to the nature of the Nix language, the system is configured completely declaratively. You specify what you want to achieve on your system, instead of specifying the step-by-step commands required to accomplish it. Using NixOS’s built-in configuration options, you can specify how you want the system to look in a manner that is logical to you. How things are built is calculated by Nix itself.

Out of the box, NixOS has 10,000 built-in options to configure your system. There is support for many popular packages and system settings. So you might not even need to write your own modules. You can search for options on NixOS Search.

Reliable, atomic upgrades

The way Nix structures packages and their dependencies means that no matter how you layout your system, your upgrades should be reliable. To makes things better, NixOS first rebuilds your system, and then on successful rebuild, switches to the new system. If the rebuild fails, your current system is untouched. This makes upgrading or configuration changes a little safer.

Rollbacks

Say your upgrade or configuration change didn’t work out. You’re now having problems. It is simple to quickly revert your last generation. Simply run nixos-rebuild switch --rollback and you’ve rolled back.

NixOS keeps your previous generations of the system until they’re garbage collected.

Garbage collection

Nix automatically keeps track of references between derivations, all the way up to pinned GC (garbage collection) roots. That means, as soon as there is no longer a generation or another derivation that refers to it, a derivation will be eligible to be garbage collected. Nix has a built-in utility to garbage collect the Nix store with different triggers such as age of generations or total disk usage by the Nix store, or even simply garbage collect paths that are safely eligible.

NixOS on AWS EC2

The NixOS project provides their own AWS AMIs (VM images), available as community AMIs. NixOS provides all the AMI IDs for these AMIs on their website and within a Nix expression in the nixpkgs repository. These specially configured images are a very useful feature for configuring the OS on your instances: you can supply a Nix configuration to the user data.

This configuration is typically what would be stored in /etc/nixos/configuration.nix. For the AMI, upon every boot, a system service replaces the contents of this file with the user data from AWS and runs a rebuild. This makes initial configuration and subsequent updates much simpler — especially when deploying in an automated fashion.

Here is an example of what you could supply to user data:

# In this example, this file has been written as a function.
# This is the single argument we are capturing to use in our configuration.
{ modulesPath, ... }:

{
# This allows us to get some EC2 specific defaults.
# The AMI needs this in order to rebuild successfully.
imports = [ "${modulesPath}/virtualisation/amazon-image.nix" ];

# This lets NixOS know which version our system installation started as.
# You usually won't change this for the life of the system's installation.
# It helps limit breakages from breaking changes in configuration options.
system.stateVersion = "23.05";

# Enable a system service to automatically garbage collect the Nix store.
nix.gc.automatic = true;

# Enable system service to automatically optimise the Nix store.
# This does things like create hard links between duplicate files.
# This saves disk space.
nix.optimise.automatic = true;

# This means that the contents of users and groups configuration files
# are congruent with your NixOS configuration.
# This has the trade-off that imperative commands such as useradd and passwd
# are unavailable. Passwords, preferably hashed, are supplied in the NixOS
# config.
users.mutableUsers = false;
# This defines a normal user with the username `nixos-user`.
users.users.nixos-user = {
isNormalUser = true;
# You can provide the hash of the password instead,
# using the `hashedPassword` option.
password = "SuperSecurePassword123";
# Also adds the user to the wheel group.
extraGroups = [
"wheel"
];
};

# Enables the OpenSSH sshd service.
# This also adds OpenSSH to the allowed ports on the system firewall.
services.openssh.enable = true;

# Enable the fail2ban service.
# This, by default, also enables an sshd jail.
services.fail2ban.enable = true;

# Accept the Let's Encrypt terms
# and sets the email to use for SSL certificates.
security.acme.acceptTerms = true;
security.acme.defaults.email = "admin+acme@version1";

# Enable the nginx service and supply a config.
# This config option also enables a service to generate
# and renew SSL certificates for the related domains.
services.nginx = {
enable = true;
virtualHosts = {
"foo.example.com" = {
forceSSL = true;
enableACME = true;
locations."/" = {
root = "/var/www";
};
};
};
};

# We will need to allow the HTTP ports on the firewall.
networking.firewall.allowedTCPPorts = [ 80 443 ];
}

The AWS AMI is a great example of the benefits NixOS provides in automation and declarativity. Something that pairs well with technologies like Hashicorp’s Terraform.

Get started with Nix standalone

To get started with Nix, you don’t need to go the full distance and set-up a NixOS installation. Nix can work standalone on both Linux and macOS. Just visit nixos.org to learn more and download.

Once Nix is installed, explore and learn the Nix system. There is lots to explore with Nix. nixos.org/learn, nix.dev, and awesome-nix are starting points for your learning. There is also the official forum at NixOS Discourse and other options to follow the Nix community at nixos.org/community.

Nix for projects

Nix, without NixOS, also has its own use in projects. Projects can provide a Nix file in the project root that can specify all the packages needed to develop and/or build the project. In a single command, Nix can enter a sub-shell with all the packages needed available in the environment. These Nix files can import the nixpkgs repository locked to a commit hash, offering the option of determinism.

These uses are typically accomplished in traditional Nix with the nix-build and nix-shell commands. Although still marked experimental, Nix also offers a new system of commands and also a new system/structure/schema called Nix Flakes.

Flakes are very much geared towards this distributable use case. Including the addition of lock files that you can commit to your repo; ensuring everyone contributing to a project is using the same derivations of packages. Nix’s Flakes offer improved reproducibility and composability over the current set-up and are already seeing strong adoption in the wider community.

Nix for your dotfiles

“Dotfiles” are a common term we use to refer to the configuration files for the programmes we use every day; such as your .bashrc or .vimrc. The Nix community maintain a popular utility that allows you to declaratively manage your home directory — just like you would manage the NixOS system. This utility is called Home Manager.

With a Nix file in ~/.config/home-manager/home.nix, you can declaratively deploy all your dotfiles and package installations. By running home-manager switch — just like nixos-rebuild switch on NixOS — the utility will build your derivations into the Nix store and symlink them out to your home directory. If you make your Home Manager config a Nix Flake, you can even configure how your dotfiles deploy on different systems.

Home Manger is a great way to give Nix a spin and experiment with it on your own. It works on NixOS and with standalone Nix on Linux and macOS. You can learn more about Home Manager on the GitHub and the homepage.

A closing note

The Nix ecosystem is big and busy. I couldn’t do it justice in one write-up alone and there are a lot of interesting things that have been missed from this piece. Make sure to give Nix a go, have a play, and break things.

About the Author:
Keir Williams is an Associate AWS DevOps Engineer at Version 1.

--

--

Keir Williams
Version 1

DevOps Engineer at Version 1. Excited about anything Linux.