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.
Use --fast instead of _NIXOS_REBUILD_REEXEC=1.
ReplyDeleteThanks for the tip! I updated the post to suggest using --fast instead
DeleteWhat a wonderful tip and post!
ReplyDeleteCurious, 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.
I'm fairly confident that this behavior is intentional and will be supported long-term
DeleteGreat 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.
ReplyDeleteFor people who don't like using root SSH, there's also the `--use-remote-sudo` option.
If now it would by default use the actual hostname of the target host for the flake output attribute - with the command lines above I'd be somewhat worried that I confuse hosts and suddenly have the mail server running where the web server should be, or so.
ReplyDeleteThis is the main reason why I have a wrapper script which ensure these match. It also selects a build host incase thats important.
Deletewonderful, i just used it in a project
ReplyDeleteVery helpful, but i have some problem when using it to deploy a nixos with a module in the flake drectory. https://discourse.nixos.org/t/how-to-deploy-custom-nixos-module-configuration-with-a-flake-using-nixos-rebuild-and-keep-the-relative-path-of-a-custom-script-file/27044
ReplyDelete