Simple deployments of NixOS machines with nixus

Since I’ve started using NixOS about four years ago I haven’t really used any tools to do central deployments of machines. But I’ve always read and known that NixOS is excellent at this. NixOS can easily build another systems configuration, then copy the system to the target systems nix store and then activate it there.

Despite knowing all this, I haven’t gotten around to doing this centrally. A while ago the need for this changed because one of my VPSes started running low on RAM, low enough to not be able to build new generations of it’s own system. Which posed a problem for future upgrades. One way to solve it would be to pay more money for resources that aren’t really needed except from when doing system upgrades. The other way would be to push pre-built systems from another location. Using the second way is simpler and fixes the issue.

There’s many ways to achieve remote deployments, one of the main and most mentioned one within NixOS is NixOps, this is because it’s the “official” tool to do this. But I’ve chose to avoid this due to the required state with it’s database to keep track of remote hosts.

Then there’s something called morph that seems fairly commonly used as well. The one I’d liked the most was nixus. Partly due to it’s simplicity, the support to do rollbacks on failed activation or failure to access the system right after activation to make sure that you’d never lock yourself out, multi system management and it’s own secrets support.

Pulling in the nixus module using niv

I’ve use niv to pull in the nixus modules:

niv add Infinisil/nixus

If you haven’t used niv before it will create a folder called nix/ in your current directory. This folder will contain a sources.nix file which parses a file called sources.json which contains the repositories managed by niv and which git commit they are locked to.

Setting up a deployment file using nixus

This is also fairly straightforward, it needs to import the nixus module from niv to then use it to declare a nixus module.

So what you need to look out for here is the relative path to the niv file sources.nix to import the niv sources.

Then you have to look out for the nixpkgs include in the defaults block, I have a checkout that I use for all the systems to have the same version of everything on all systems. Then I move all systems forward at the same time when I do upgrades.

let
  # Load niv sources
  sources = import ../nix/sources.nix;

  # Import the nixus module
  nixus = import "${sources.nixus}/default.nix" { };
in
nixus ({ config, ... }: {
  # Set a nixpkgs version for all nodes
  defaults = { ... }: {
    # Use a local nixpkgs checkout
    nixpkgs = ../nix/nixos-unstable;

    # Alternative: Fetch nixpkgs with fetch tarball
    nixpkgs = fetchTarball {
      url = "https://github.com/NixOS/nixpkgs/tarball/16fc531784ac226fb268cc59ad573d2746c109c1";
      sha256 = "0qw1jpdfih9y0dycslapzfp8bl4z7vfg9c7qz176wghwybm4sx0a";
    };
  };

  nodes.node1 = { lib, config, ... }: {
    # How to reach this node
    host = "root@node1.example.org";

    # What configuration it should have
    configuration = ../hosts/node1/configuration.nix;
  };

  nodes.node2 = { lib, config, ... }: {
    # How to reach this node
    host = "root@node2.example.org";

    # What configuration it should have
    configuration = ../hosts/node2/configuration.nix;
  };
})

Building and deploying systems

Building and deploying your system couldn’t be simpler! It’s as easy as running nix-build path/to/deployment.nix where the path is to the file that I had an example of above. When the build is done you’ll have a result symbolic link in the folder you were executing the command from. This results output is a shell script that will run the deploy. So just go ahead and run ./result to deploy to all the nodes defined in that file.