Monday, January 23, 2023

Announcing nixos-rebuild: a "new" deployment tool for NixOS

Announcing nixos-rebuild: a "new" deployment tool for NixOS

The title of this post is tongue-in-cheek; nixos-rebuild is a tool that has been around for a long time and there’s nothing new about it. However, I believe that not enough people know how capable this tool is for building and deploying remote NixOS systems. In other words, nixos-rebuild is actually a decent alternative to tools like morph or colmena.

Part of the reason why nixos-rebuild flies under the radar is because it’s more commonly used for upgrading the current NixOS system, rather than deploying a remote NixOS system. However, it’s actually fairly capable of managing another NixOS system.

In fact, your local system (that initiates the deploy) doesn’t have to be a NixOS system or even a Linux system. An even lesser known fact is that you can initiate deploys from macOS using nixos-rebuild. In other words, nixos-rebuild is a cross-platform deploy tool!

The trick

I’ll give a concrete example. Suppose that I have the following NixOS configuration (for a blank EC2 machine) saved in configuration.nix:

{ modulesPath, ... }:

{ imports = [ "${modulesPath}/virtualisation/amazon-image.nix" ];

  system.stateVersion = "22.11";
}

… which I’ve wrapped in the following flake (since I like Nix flakes):

{ inputs.nixpkgs.url = "github:NixOS/nixpkgs/22.11";

  outputs = { nixpkgs, ... }: {
    nixosConfigurations.default = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";

      modules = [ ./configuration.nix ];
    };
  };
}

Further suppose that I have an x86_64-linux machine on EC2 accessible via ssh at root@example.com. I can deploy that configuration to the remote machine like this:

$ nix shell nixpkgs#nixos-rebuild
$ nixos-rebuild switch --fast --flake .#default \
    --target-host root@example.com \
    --build-host root@example.com

… and that will build and deploy the remote machine even if your current machine is a completely different platform (e.g. macOS).

Why this works

The --fast flag is the first adjustment that makes the above command work on systems other NixOS. Without that flag nixos-rebuild will attempt to build itself for the target platform and run that new executable with the same arguments, which will fail if the target platform differs from your current platform.

The --build-host flag is also necessary if the source and target platform don’t match. This instructs nixos-rebuild to build on the target machine so that the deploy is insensitive to your current machine’s platform.

The final thing that makes this work is that Nixpkgs makes the nixos-rebuild script available on all platforms, despite the script living underneath the pkgs/os-specific/linux directory in Nixpkgs.

Flakes

There’s a reason why I suggest using flakes alongside nixos-rebuild: with flakes you can specify multiple NixOS machines within the same file (just like we can other NixOS deployment tools). That means that we can do something like this:

{ inputs.nixpkgs.url = "github:NixOS/nixpkgs/22.11";

  outputs = { nixpkgs, ... }: {
    nixosConfigurations = {
      machine1 = nixpkgs.lib.nixosSystem { … };

      machine2 = nixpkgs.lib.nixosSystem { … };

      …
    };
  };
}

… and then we can select which system to build with the desired flake URI (e.g. .#machine1 or .#machine2 in the above example).

Moreover, by virtue of using flakes we can obtain our NixOS configuration from somewhere other than the current working directory. For example, you can specify a flake URI like github:${OWNER}/${REPO}#${ATTRIBUTE} to deploy a NixOS configuration hosted on GitHub without having to locally clone the repository. Pretty neat!

Conclusion

I’m not the first person to suggest this trick. In fact, while researching prior art I stumbled across this comment from Luke Clifton proposing the same idea of using nixos-rebuild as a deploy tool. However, other than that stray comment I couldn’t find any other mentions of this so I figured it was worth formalizing this trick in a blog post that people could more easily share.

This post supersedes a prior post of mine where I explained how to deploy a NixOS system using more low-level idioms (e.g. nix build, nix copy). Now that nixos-rebuild supports both flakes and remote systems there’s no real reason to do it the low-level way.

Edit: An earlier version of this post suggested using _NIXOS_REBUILD_REEXEC=1 to prevent nixos-rebuild for building itself for the target platform but then Naïm Favier pointed out that you can use the --fast flag instead, which has the same effect.

5 comments:

  1. Use --fast instead of _NIXOS_REBUILD_REEXEC=1.

    ReplyDelete
    Replies
    1. Thanks for the tip! I updated the post to suggest using --fast instead

      Delete
  2. What a wonderful tip and post!

    Curious, is this intended behaviour in nixos-rebuild or is it on its way out and accidental? Curious for longterm maintainability when proposing this deploy method within teams.

    ReplyDelete
    Replies
    1. I'm fairly confident that this behavior is intentional and will be supported long-term

      Delete
  3. Great post. I've been using this deployment method for a couple years now and it is very good. It's especially nice to augment it with a secret management system like agenix or sops-nix that work without any special deployment features.

    For people who don't like using root SSH, there's also the `--use-remote-sudo` option.

    ReplyDelete