I have a headless server (an Intel NUC). I am migrating from an Arch Linux machine to NixOS. These steps are not intended to be followed by anyone other than myself, however, I’ve tried to add context to explain what I’m doing.

Key Decisions

  1. I don’t plan to encrypt my drive.

Primarily so that if I have a power blip at home while I’m away, I don’t have to worry about not being able to decrypt the drive on startup in order to be able to login remotely.

believe there are ways to be able to decrypt the drive automatically on startup if a ubikey or USB is plugged in, but I haven’t looked too deeply and I need to get my machine working immediately. This is something I can revisit later.

  1. I wanted a repository / project structure that would allow for setting some shared configuration across machines, architectures, users, etc. and would allow overrides.

These projects where the ones I referred to most during my setup:

You might find others that you like here.

I wanted to have a base “headless” config and then a GUI config for other machines.

Personal Takeaways

I love the “feel” of running nixos-rebuild switch and things just working. I would say, in general though, things still feel a bit early — even though Nix and NixOS is not new; Flakes are still experimental meaning APIs could change, documentation and examples have some gaps and there seems to be a serious lack of clear standards in terms of project structure.


Preparing a bootable USB

  1. Re-partition the USB just to wipe it.
sudo fdisk /dev/sda
(fdisk): g
(fdisk): n (skip, skip, +512M)
(fdisk): t (1)
(fdisk): n (skip, skip, skip)
(fdisk): w
  1. Check the partitions
sudo parted /dev/sda print
  1. Reformat the USB
sudo mkfs.fat -F32 -nESP /dev/sda1
sudo mkfs.ext4 /dev/sda2
  1. Load the NixOS iso onto the USB
sudo dd bs=4M if=/home/mccurdyc/Downloads/nixos-minimal-22.05.2485.3d47bbaa26e-x86_64-linux.iso of=/dev/sda status=progress

Partitioning Target Machine

  1. Check out what drives we have.
  1. Partition drives.

I’m going to make a boot and root partition. In the past, I had a separate root (25G) and home partition and ran into many issues with tools like Docker (easily fixable by changing image location) and Arch’s package manager filling up the root partition.

sudo fdisk /dev/nvme0n1
(fdisk): g
(fdisk): n (skip, skip, +512M)
(fdisk): t (1)
(fdisk): n (skip, skip, skip)
(fdisk): w
  1. Check the partitions
sudo parted /dev/nvme0n1 print
  1. Reformat the target drives
sudo mkfs.fat -F32 -nESP /dev/nvme0n1p1
sudo fatlabel /dev/nvme0n1p1 NIXBOOT
sudo mkfs.ext4 /dev/nvme0n1p2 -L NIXROOT
  1. Mount the target drives

sudo mount /dev/disk/by-label/NIXROOT /mnt
sudo mkdir -p /mnt/boot
sudo mount /dev/disk/by-label/NIXBOOT /mnt/boot
  1. Generate the NixOS configuration on the target filesystem!!!

sudo nixos-generate-config --root /mnt
  1. Tweak the configuration.
  1. NixOS install
cd /mnt
sudo nixos-install


Building a New Generation

  1. Build
sudo nixos-rebuild build
sudo nixos-rebuild switch
sudo nixos-rebuild switch --flake '.#intel' # flakes

I love this command!! It just feels so good! It feels immutible.

  1. Rollbacks
nix-env --rollback               # roll back a user environment
nixos-rebuild switch --rollback  # roll back a system environment

Home Manager

  1. Install home-manager
sudo nix-channel --add home-manager
sudo nix-channel --add nixos
sudo nix-channel --update
  1. Create file
sudo nix-shell '<home-manager>' -A install
# /home/mccurdyc/.config/nixpkgs/home.nix
  1. Make a change; run
home-manager build
home-manager switch
  1. After migrating to Flakes, I no longer have to manage this separately.
sudo nixos-rebuild switch --flake '.#intel'

References / Notes

Formatters for Nix lang

If I did this all again, I would have setup a formatter first so that rebasing commits after would be easier. It probably wouldn’t have made a huge difference because I ended up just editing the files during the rebase anyway.

I ended up choosing alejandra as my formatter of choice, but may end up switching to nixpkgs-fmt because the experimental Nix LSP client [rnix-lsp]( uses nixpkgs-fmt.


  • still in beta phase

  • Flakes allow you to specify your code’s dependencies (e.g. remote Git repositories) in a declarative way

  • Enabling Flakes

    # /etc/nixos/configuration.nix
    nix.settings.experimental-features = [ "nix-command" "flakes" ];
  • With the help from @phamann, I was able to setup my Flake to manage my Vim plugins in this commit.

  • Note that any file that is not tracked by Git is invisible during Nix evaluation, in order to ensure hermetic evaluation.
    • I really liked this aspect.
  • The inputs attribute specifies other flakes that this flake depends on. These are fetched by Nix and passed as arguments to the outputs function.
  • The outputs attribute is the heart of the flake: it’s a function that produces an attribute set.
  • The self argument denote this flake. Its primarily useful for referring to the source of the flake (as in src = self;) or to other outputs (e.g. self.defaultPackage.x86_64-linux)
  • The attributes produced by outputs are arbitrary values, except that (as we saw above) there are some standard outputs such as defaultPackage.${system}.
  • nixos-rebuild switch --flake /path/to/my-flake#my-machine builds and activates the configuration specified by the flake output If you omit the name of the configuration (#my-machine), nixos-rebuild defaults to using the current host name.

  • NixOS is currently built around a monorepo workflow — the entire universe should be added to the nixpkgs repository, because anything that isn’t, is much harder to use.

  • nix flake show templates

    # nix flake show templates
    ├───defaultTemplate: template: A very basic flake
        ├───bash-hello: template: An over-engineered Hello World in bash
        ├───c-hello: template: An over-engineered Hello World in C
        ├───compat: template: A default.nix and shell.nix for backward compatibility with Nix installations that don't support flakes
        ├───full: template: A template that shows all standard flake outputs
        ├───go-hello: template: A simple Go package
        ├───haskell-hello: template: A Hello World in Haskell with one dependency
        ├───hercules-ci: template: An example for Hercules-CI, containing only the necessary attributes for adding to your project.
        ├───pandoc-xelatex: template: A report built with Pandoc, XeLaTex and a custom font
        ├───python: template: Python template, using poetry2nix
        ├───rust: template: Rust template, using Naersk
        ├───rust-web-server: template: A Rust web server including a NixOS module
        ├───simpleContainer: template: A NixOS container running apache-httpd
        └───trivial: template: A very basic flake
  • nix flake init -t templates#simpleContainer

  • Nixpkgs overlays add or override packages in the pkgs set.

direnv + nix-shell

In this commit, I started using direnv and defined a flakify shell function for easily adding direnv config with Flake support to repos.

Here is an example use of nix-direnv with a Flake for my website repo which has gcloud and hugo as build dependencies.

Home Manager programs.${program} Configuration Steps

Headed Server

(Haven’t got here yet. This part of the post will be updated once I get here.)

Other Thing to Look Into

General References