Reliable and reproducible Linux installation with NixOS
Feb 8, 2022
Never miss our publications about Open Source, big data and distributed systems, low frequency of one email every two months.
When using an operating system, upgrading packages or installing new ones are common tasks that introduce the risk of affecting the stability of the system. NixOS is a Linux distribution that ensures the reliability of the operating system and allows easy reproducibility of the system current and previous states.
This article follows our Nix introduction and deploy NixOS on your machine. It explains how NixOS works, how to obtain and install it, and how Nix ensure reliability. Your machine will boot with a working NixOS system and you will gain knowledge on how NixOS, the Nix package manager, and Home Manager interact together.
What is NixOS?
NixOS is a Linux distribution. It is built on top of Nix, a functional package manager, which language is inspired by functional programming. NixOS takes the power of the Nix package manager and applies it to the entire system. That means, among other things, it is easy to roll back the whole configuration of the system to an earlier state. Complementary to the system managed by NixOS, Home Manager manages a user environment.
Why NixOS?
NixOS applied Nix fundamentals to the entire system. That leads to:
- System reproducibility: given a specification of a system (in a NixOS configuration file), it is possible to reproduce the entire system (modulo mutable state, such as the contents of databases for example) to another machine.
- Atomic upgrades and rollbacks: changes made at the system level or the package level are always revertible.
- Dependencies management: NixOS uses the Nix package management. Nix ensures that the dependencies declaration is complete when installing a package. Because Nix stores packages in isolation between them, it is possible to have different versions of the same package installed. So different packages can then use different versions of the same dependency without a problem. That management of dependencies does not lead to apps with huge sizes as it is the case using flatpack.
Installation of NixOS
The machine used during the installation is a Dell Precision 5520 laptop with 1TB SSD and 32GB RAM. The instructions shall apply to any machine, whether it is a development computer, a laptop or a virtual machine.
Obtaining NixOS ISO
NixOS ISO image can be downloaded from the NixOS download page. The ISO image file is available in two options:
- The graphical ISO image (the easiest choice): with this option, the installation is simpler as it has the graphical interface and the networking, ready to use, needed for the installation.
- The Minimal ISO image (non-graphical): this is the minimalistic ISO image in terms of content. The advantage is the lower size of the ISO image. But the drawback is that there is more to prepare before the installation. Here is how to prepare the networking in the installer.
Networking must be configured before the installation to download the requested dependencies.
My installation uses the graphical ISO image with the Gnome desktop environment. The size is relatively small, about 2GB.
Boot the installer
The .iso
disk image is used to create a bootable USB drive. The NixOS official documentation covers the process. Follow the Ubuntu documentation for a more user-friendly approach using balenaEtcher.
Once completed, restart your targeted machine and boot from the USB drive. The screen presents a graphical interface from where NixOS can be configured and installed. A first screen proposes several variations of the installer, select the first proposition. A few seconds later, Gnome is up and running from the USB system. Open a new terminal.
Partitioning
NixOS installer doesn’t do any partitioning or formating. It is the user responsibility. To perform this operation, it is necessary to know the hard drive’s name. The command below helps to know the hard drive’s name:
lsblk -p
In our case of installation, the hard drive’s name was /dev/nvme0n1
. But depending on the disk type (SATA, SSD, NVMe, …), it is possible to have alternative values such as /dev/sda
. For the next of this article, commands are based on the drive name /dev/nvme0n1
.
Once the drive’s name is known, the next step is partitioning. In our case, a single partition is entirely dedicated to the operating system. Hibernation across reboot persists the system state on disk into swap space. Thus, it requires the creation of a swap partition. It is not recommended to enable hibernation on system with large RAM resources such as a server. If you choose to enable hibernation, set the swap size to equal 1.5 times the RAM size.
The UEFI partition scheme is used as the booting method. The swap partition uses 50 GiB. The MBR partition scheme is also presented for illustration purpose.
From the terminal, log in as root
with sudo su -
. Both fdisk and Parted are valid tools to partition the drive.
-
Example partition scheme for NixOS on /dev/nvme0n1 with UEFI
# Enter fdisk fdisk /dev/nvme0n1 # Partition 1 g # Create a new empty GTP partition table n # Create a new partition 1 # Partition number of the first partition., default (default) # First sector, default 2048 +512M # Last sector, 512 MB t # Change the partition type 1 # Select the first partition 1 # Use EFI System Partition, or ESP (default) # Partition 2 n # Create a new partition 2 # Partition number of the second partition, default (default) # First sector start at the end of first partition, default 1001472 -50G # Last sector, all disk minus 50G for swap # Partition 3 n # Create a new partition 3 # Third partition (default) # First sector start at the end of second partition (default) # Last sector is the end of the disk t # Change the partition type 3 # Select the third partition 19 # Use Linux swap # Finalize v # Check the settings w # Write and quit
-
Alternative example partition scheme for NixOS on /dev/nvme0n1 with MBR
parted /dev/nvme0n1 -- mklabel msdos parted /dev/nvme0n1 -- mkpart primary 1MiB -50GiB parted /dev/nvme0n1 -- mkpart primary linux-swap -50GiB 100%
Formatting
Quick recap, in our installation, NixOS targets the /dev/nvme0n1
disk. The /dev/nvme0n1p2
partition is the root of the Linux system. The /dev/nvme0n1p3
partition is the swap drive.
In this step, the objectives are to format the partitions, activate the swap partition and mount the target file system on which NixOS is going to be installed. Here are commands for UEFI and MBR (Legacy boot) booting methods:
-
UEFI case
# Disk format mkfs.ext4 -L nixos /dev/nvme0n1p2 mkswap -L swap /dev/nvme0n1p3 mkfs.fat -F 32 -n boot /dev/nvme0n1p1 # System mount mount /dev/disk/by-label/nixos /mnt swapon /dev/nvme0n1p3 mkdir -p /mnt/boot mount /dev/disk/by-label/boot /mnt/boot
-
MBR case
mkfs.ext4 -L nixos /dev/nvme0n1p1 mkswap -L swap /dev/nvme0n1p2 mount /dev/disk/by-label/nixos /mnt swapon /dev/nvme0n1p2 nixos-generate-config --root /mnt
NixOS configuration
Installation is done via the NixOS configuration file in /mnt/etc/nixos/configuration.nix
. The commands to generate the configuration file and open it for edition:
nixos-generate-config --root /mnt
nano /mnt/etc/nixos/configuration.nix
In the NixOS philosophy, the NixOS configuration file reflect the entire system. It includes the packages to install, the service to run, the settings to apply, the network configuration, and potentially a lot more. To make this introduction easier to grasp, we will start with a minimal configuration and then complete it once the system reboot. In the future, you are encourage to commit this configuration. This way, on a new machine installation, you have the possibilty to clone your configuration and re-apply it, or a subset of it, to a new targeted environnment.
A minimal NixOS configuration file targetting the Gnome desktop environment and the UEFI booting method is shown below. If you wish to start with a more complete system, you can enrich the configuration with your own properties or use the more exhaustive configuration file proposed at the end of this article.
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
# Per-interface useDHCP will be mandatory in the future, so this generated config
# replicates the default behaviour.
networking.useDHCP = false;
networking.interfaces.wlp2s0.useDHCP = true;
networking.networkmanager.enable = true;
# Enable the X11 windowing system.
services.xserver.enable = true;
# Enable the GNOME Desktop Environment.
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
# Configure keymap in X11
services.xserver.layout = "fr";
# Enable sound.
sound.enable = true;
hardware.pulseaudio.enable = true;
# Enable touchpad support (enabled default in most desktopManager).
services.xserver.libinput.enable = true;
# Define a user account.
# Don't forget to set a password with ‘passwd’ using the root account if you don't use the initialPassword field.
users.users.florent = {
isNormalUser = true;
initialPassword = "secret"; # Define the user initial password
extraGroups = [ "wheel" ]; # wheel to enable ‘sudo’ for the user.
};
# List packages installed in system profile.
# To search, run: `nix search wget`
environment.systemPackages = with pkgs; [
vim
];
# Enable the OpenSSH daemon.
services.openssh.enable = true;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "21.05"; # Did you read the comment?
}
Installation
This is the last step before rebooting into the system. Internet connection is required to download dependencies. The installation reflects the content of the configuration created previously. The command to launch the installation is:
nixos-install
A root password is requested. Once completed, the system is ready on restart.
Changing the NixOS configuration
When the system is up, the system will evolve with your needs. New tools are installed, services are started and configuration are updated. This is part the system lifecycle whether the system targets a development machine or a production server.
The NixOS configuration file reflects the configuration at the system level, affecting all users created on the machine. In addition, Home manager works at the user level. It installs software and configuration for a specific user.
Adding the package curl
at system level is done with the configuration below:
{ config, pkgs, ... }:
{
...
environment.systemPackages = with pkgs; [
vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
curl
];
...
}
Any change in the NixOS configuration file leads to a new booting configuration. The commands below build the configuration declared in the NixOS configuration file and make it the default configuration for booting:
nixos-rebuild switch
At any application of the command nixos-rebuild switch
, a new boot configuration is available at the start of the operating system. Here is an example screen on reboot:
Command to list the booting configurations on NixOS:
sudo nix-env -p /nix/var/nix/profiles/system --list-generations
Our Nix introduction lists the most common commands.
What is Home Manager?
Home Manager is a tool to manage a user environment using Nix package manager. As such, it completes NixOS. There are two ways to use Home Manager:
-
Using the standalone home-manager tool
It allows managing the home directory of a user independently of the system as a whole. There are two configuration files to maintain: one file for the configuration at system-level (
/etc/nixos/configuration.nix
) and one file for the configuration at user-level (~/config/nixpkgs/home.nix
). The former requires root privileges while the later is executed by the user withoutsudoers
permissions. -
As a module within a NixOS system configuration
It allows to manage system-level configuration and user-level configuration within a single file (
/etc/nixos/configuration.nix
). Root level privileges are required to apply Home Manager updates.
I found it easier to maintain my system configuration in one single file. After all, I am the only user of my development machine. We cover below the installation of Home Manager as a module within the NixOS system configuration.
Home Manager as a module of NixOS
Installation of Home manager as NixOS module requires root-level privileges. From the terminal, log in as root
with sudo su -
. Then follow the steps below to set up Home Manager:
-
Use the commands below to add the Home Manager channel:
nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager # If you follow a Nixpkgs version 21.11 channel, run nix-channel add https://github.com/nix-community/home-manager/archive/release-21.11.tar.gz home-manager instead of the command below nix-channel --update
-
Add
in theimports
section of the NixOS configuration file. A new NixOS option calledhome-manager.users
is now availble.
Given the example of NixOS configuration file in section Generation and configuration of the NixOS configuration file, adding Home Manager module to install the python3
package and to configure the dotfile .git
for a user named florent
gives:
{ config, pkgs, ... }:
{
imports =
[
./hardware-configuration.nix
# To introduce the NixOS option called home-manager.users <home-manager/nixos> ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.useDHCP = false;
networking.interfaces.wlp2s0.useDHCP = true;
networking.networkmanager.enable = true;
services.xserver.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
services.xserver.layout = "fr";
sound.enable = true;
hardware.pulseaudio.enable = true;
services.xserver.libinput.enable = true;
users.users.florent = {
isNormalUser = true;
initialPassword = "titi" # Define the user initial password
extraGroups = [ "wheel" ]; # wheel to enable ‘sudo’ for the user.
};
# Home Manager configuration for user florent home-manager.users.florent = { pkgs, ...}: { # List packages to install in user profile home.packages = [ pkgs.python3 ]; # Git config for the user programs.git = { enable = true; userName = "Florent"; userEmail = "florent@adaltas.com"; }; };
environment.systemPackages = with pkgs; [
vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
curl
];
services.openssh.enable = true;
system.stateVersion = "21.05"; # Did you read the comment?
}
Like previously, use nixos-rebuild switch
to apply changes.
Conclusion
NixOS applies Nix fundamentals to the entire system for a holistic Nix experience. Nix simplify the process of saving, sharing or replicating the configuration of machines. Applied to the whole system, it creates a flexible, reliable and reproductible Linux distribution. One can easily imagine the appeal of these benefits applied to CI/CD environments and distributed clusters.