OpenTofu/Terraform provider for NixOS

Hello everyone,

Forgive my naivety here, but is there any kind of opentofu/terraform provider available for nixos machines? I am aware that proxmox has this, and I have taken a look at running openstack on nixos as a possible option without much luck. I know you can technically write nix code which can manage VMs and networking, and this is what I’ve been doing in my home lab, but opentofu seems like it would be a much cleaner and platform agnostic solution at scale.

Where I see nixos potentially being of benefit in this ecosystem is for managing the bare metal machines in the fleet (such as with colmena) as well as building VM/OCI container images for deployment in the cloud environment.

Curious to know what people’s thoughts are, I’m going based on impressions mainly.

2 Likes

I think the standard for deploying NixOS VMs is to provision cloud VMs with a small base NixOS image and start deploying them with Colmena/deploy-rs (#1 deploy-rs shill here, it’s awesome).

But it seems like you actually want another piece of software running on some NixOS hosts to run VMs. As a fan of K8s, I recommend k3s with Kubevirt, using Flux for CD, but you can also look into using LXC/Incus or Proxmox-NixOS (seemingly unmaintained).

2 Likes

Oh cool! Yeah k3s + kubevirt looks a lot closer to what I was envisioning. Maybe I’ll take a shot at that before playing with anything more complicated :stuck_out_tongue:

1 Like

Yeah, if you’re comfortable with Kubernetes it’s a pretty good setup. and the NixOS services.k3s option is pretty good at configuring it, and you may want to take a look at Flux for deploying stuff onto it.

NixOS Anywhere has a Terraform/Open Tofu module exactly for that. It’s useful for provisioning the initial NixOS VMs, but then you can go with whatever you like to actually manage the deployed infra later.

What I usually do is have a small “bootstrap profile” (a basic user with a know pub key, other services that should be up from day 0) that gets provisioned first and the run deploy-rs (already mentioned here) to actually deploy the "main flake”. So it’s a two-stage process.

Here’s an example of how it can be done, I used this to deploy NixOS VMs to both AWS and Magalu Cloud (a Brazilian cloud provider). This one doesn’t use deploy-rs, but it uses two steps to get the initial infra setup.

2 Likes

I use opentofu to create a VPS which automatically gets a minimal nixos installed which retains SSH access (via nixos-bite). After that, I use the tofu’s output ip to apply a nix configuration.

Finally, everything else coming later is deployed with deploy-rs.

2 Likes

Just to throw in an alternative PoV: I use terraform, but I treat all infra as immutable, so the instance is built by pulling a user-data script on start, and switching once. Make a new config, build new instances. Obviously there are limits to this approach if the VM has state, but then you can do things like attach/detach a volume, and separate the lifecycle of your compute from storage (which is a good idea anyway). This massively limits the scope of what you need terraform to do, as it basically doesn’t need to know about nixos at all.

3 Likes

Interesting approach! Do you have any more detailed examples or blog posts about this? Would be nice to see more concrete code examples of how this works, especially with load-balancing/switchover between instances.

I don’t have a blog. This is perhaps over-the-top for many people, I guess it depends on exactly the setup. Given that the OP was talking about proxmox I was assuming some kind of multi-vm tenancy. At which point how cut-over works depends on the type of load balancer is in play. We use AWS, so standard health checking on an application or network load balancer + instance refresh (cycle all of instances by destroying and recreating them) does everything you need. You could in principle do this without an LB just by having cloud-init (or whatever metadata service) end finish swapping the dns entry. This only works if you keep your DNS ttls low (~ 60s). Of course you don’t need AWS, you can have haproxy or similar act as a reverse proxy and update its config as things shuffle about (lots of ways to skin that cat). The short of it is: if you already use any kind of LB setup with any kind of dynamism, just switch on startup, nothing extra to do. If you are running something with an uptime objective, you probably already have this. If you don’t, then using deploy-rs on a single box and rebuilding in place probably makes a lot of sense.

One last thing: switch on startup allows you to get away with using tiny boxes that can’t support a switch while their services are running.

https://github.com/NixOS/nixpkgs/blob/3e115947440d81a8c297d743aace06c19d1cc591/nixos/modules/virtualisation/amazon-init.nix

is the code we (used to, we since cut over to our own version) rely on to make this work on boot. I am sure there is a cloud-init compatible version either written or possible.

2 Likes

That’s cool. Pretty neat setup! I definitely like the idea of avoiding in-place swaps on running machines, especially since I’m a k8s fan, where pods are immutable and managed via Deployment and friends.

Interesting, does this provision VMs and networking on a nixos hypervisor or does this require some other cloud setup? I’m basically trying to go for “diy cloud” across my machines.

My big challenge right now is networking. Making sure everything is consistently setup across hypervisor/vm configs is proving to be a bit of a pain.

1 Like

Just to throw in an alternative PoV: I use terraform, but I treat all infra as immutable, so the instance is built by pulling a user-data script on start, and switching once. Make a new config, build new instances. Obviously there are limits to this approach if the VM has state, but then you can do things like attach/detach a volume, and separate the lifecycle of your compute from storage (which is a good idea anyway). This massively limits the scope of what you need terraform to do, as it basically doesn’t need to know about nixos at all.

I think this is close to what I had in mind. I basically want to set up CI jobs to create immutable VM images and then have services within them which fetch other images such as OCI containers to run from another CI output location. I also want to have a clean separation between storage and the rest of the VM’s system.

1 Like

We use packer to get 99% of the way there with our base image, then a small user-data script (tied to whatever launch template the autoscale group uses) to do the last couple of percent. Avoids having hundreds of AMIs to manage, but certainly with the right automation managing the AMIs is probably not that bad.

1 Like

Interesting, does this provision VMs and networking

Short answer: No. You’d still require some third party provider (like dmacvicar/terraform-provider-libvirt) to create the actual VM (CPU, RAM, virtual disks) and the host-level networking (virtual bridges, tap interfaces, VLANs).

does this require some other cloud setup

As far as I remember, NixOS anywhere doesn’t require a cloud setup, once the VM exists, has an IP, and is booted into a live environment, it takes over to wipe the virtual drive and installs NixOS in it (that’s the default module), but you can have a lot of control on which stage is run (like nix-build if you just want to run nix build inside some Terraform / Tofu workflow or install if you really just want to run nixos-install pointing to a particular VM), the config options for each of these modules tends to be very flexible.

Maybe what makes sense for you is leveraging what used to be the nixos-generators project (and is now part of the nixos-rebuild build-image command) to create your immutable nixos images first.

My big challenge right now is networking. Making sure everything is consistently setup across hypervisor/vm configs is proving to be a bit of a pain.

You could use the aforementioned libvirt provider in OpenTofu to provision the VMs and the networking bridges on your NixOS host. Once the provider creates the VM, you pass its IP address to the nixos-anywhere Terraform module to install the OS (or to just run nixos-rebuild with a particular flake you want to deploy).

As a disclaimer, I have never used that particular provider before, but I see no reason why something like this isn’t possible:

# I'm assuming all the networking is already defined elsewhere in Terraform / Tofu
# (...)
# Spin up VM from pre-built image
resource "libvirt_domain" "app_server" {
  name = "app-01"
  disk {
    volume_id = libvirt_volume.nixos_base_image.id
  }
}

# (...)

# Apply config updates over SSH using only the rebuild submodule
module "nixos_rebuild" {
  source = "github.com/nix-community/nixos-anywhere//terraform/nixos-rebuild"

  target_host        = libvirt_domain.app_server.network_interface[0].addresses[0]
  nixos_system_attr  = ".#nixosConfigurations.app01.config.system.build.toplevel"
}

So, you get a “cloud-like” workflow, the annoying part is the chicken-egg problem of how small you want your initial infra to be (and then use it to bootstrap everything with NixOS). That would depend if your hypervisor is already running on NixOS as well, which I don’t remember being it mentioned yet.

Another headache is the usual place where you want to put the Terraform / Tofu state, a cool new solution nowadays is stategraph, it solves the annoying problem Terraform always had with that pesky json file and ships the state to PostgreSQL instead (where you can actually query it in a normal way). DISCLAIMER: I USED TO WORK AT TERRATEAM, WHICH LATER BECAME STATEGRAPH, I still vouch for their culture of being open and always making self-hosting an option.

1 Like

Building and Importing NixOS AMIs on EC2 | Blog | jackkelly.name contains a worked example for baking an AMI with Packer, in case it is useful for anyone else reading this thread.

1 Like

This seems like a very open-ended question.

It seems like most people have answered similar to what I would:

You can provision cloud VMs with terraform/opentofu, initialize them as NixOS machines in a variety of ways (cloud-init, pre-configured images, nixos-anywhere), and update them using another set of ways (colmena, deploy-rs, etc.).

You can also host Kubernetes nodes; k3s is nice and easy to start with, but if you’re serious about KubeVirt, you may want to ditch NixOS and use KubeVirt straight from Talos Linux, and run NixOS inside guest VMs instead. Or you may want to use various hypervisors from NixOS to manage virtual machines. Another option than Incus (which I’ve heard great things aobut) is using NixOS as Dom0 in a Xen hypervisor:

After all of these options, can you clarify what you were going for?

What’s the actual benefit of using Packer over nixos-rebuild build-image/config.system.build.images.amazon?

What makes talos good for kubevirt vs Nixos?

It generates an image using the cloud provider’s APIs by snapshotting the machine it spawned to perform the build. If you build the image with commands, you will have to then get the raw disk image into a cloud provider’s image (e.g. Amazon Machine Image (AMI)) by uploading the raw image to S3 and using a service like VM Import/Export to do the conversion.

Aside from slightly increased convenience, I still feel that config.system.build.images.amazon+VM Import is better and way more Nix-y. Packer is still running impure commands in a VM.