Skip to content

Deploy and infrastructure batteries

A fleet that builds NixOS hosts often also deploys them, writes DNS records, writes Kubernetes manifests and exports modules for other flakes. Batteries do each of these from a sink. This example uses all of them. The fleet spawns hosts, a cluster and an export. Each is a kind with its own sinks.

modules/fleet.nix
# Hosts and a cluster in one environment. Hosts build NixOS and join a colmena hive; deploy-rs reads the built configs.
{
yen,
lib,
inputs,
...
}:
let
b = yen.lib.batteries;
in
{
yen.kinds.fleet = {
spawns = {
host = _: yen.host;
cluster = _: yen.cluster;
export = _: yen.export;
};
sinks = {
out.reduce = yen.lib.reduce.merge;
# each host gives its own NixOS modules to the hive
colmena = {
# (1)
reduce = yen.lib.reduce.merge;
out = b.deploy.colmena {
from = yen.kinds.fleet.sinks.colmena;
inherit (inputs.colmena.lib) makeHive;
meta.nixpkgs = inputs.nixpkgs.legacyPackages.x86_64-linux;
};
};
# dns records as terraform, built with terranix
terraform.out = b.terranix.configuration {
# (2)
from = yen.kinds.fleet.sinks.terraform;
inherit (inputs.terranix.lib) terranixConfiguration;
system = "x86_64-linux";
name = _: "dns";
};
# machines write NixOS config into each other (oddlama's nodes.<name>)
nodes.reduce = yen.lib.reduce.elements; # (3)
};
};
yen.kinds.host = {
fields.addr = yen.lib.str;
sinks.nixos.out = b.nixos.system {
# (4)
from = yen.kinds.host.sinks.nixos;
inherit (inputs) nixpkgs;
};
};
yen.kinds.cluster.sinks.k8s.out = b.k8s.manifests {
# (5)
from = yen.kinds.cluster.sinks.k8s;
inherit lib;
};
# an export never builds a machine: what lands on it is nixosModules.<name> for other flakes
yen.kinds.export.sinks.nixos.out = b.exports.modules {
# (6)
from = yen.kinds.export.sinks.nixos;
output = "nixosModules";
};
yen.fleet.main = { };
yen.host = {
lb.addr = "10.0.0.1";
web.addr = "10.0.0.2";
};
yen.cluster.axon = { };
yen.export.base = { };
# deploy-rs: one node per built NixOS config
yen.flake =
{ out, ... }:
out
// b.deploy.deploy-rs {
# (7)
configurations = out.nixosConfigurations;
hostname = n: _: yen.host.${n}.addr;
activate = _: c: inputs.deploy-rs.lib.x86_64-linux.activate.nixos c;
};
}

Each battery is the value of one key (out, reduce or a NixOS key). It gets the sink it reads as a ref in from.

  • (1) collects the modules of every host in a colmena hive. The out key builds colmenaHive.
  • (2) collects terraform modules and builds terraformConfigurations.dns with terranix.
  • (3) has no out. Machines give modules to each other through it, and elements keeps who gave what.
  • (4) builds nixosConfigurations for each host.
  • (5) merges the manifests of a cluster with the module system.
  • (6) is an export. It builds no machine: it gives nixosModules.<name> for other flakes.
  • (7) is a function in yen.flake. It gives one deploy-rs node for each built NixOS configuration.

The aspects fill these sinks. machine is active at every host and at the export base. hive gives to the sinks colmena, terraform and nixos.

modules/machines.nix
{ yen, ... }:
let
b = yen.lib.batteries;
in
{
yen.aspects.machine = {
at = [
yen.kinds.host
yen.export.base # the same base, exported for other flakes
];
nixos = {
boot.loader.grub.device = "nodev";
fileSystems."/" = {
device = "/dev/sda1";
fsType = "ext4";
};
nixpkgs.hostPlatform = "x86_64-linux";
system.stateVersion = "25.05";
};
};
yen.aspects.hive = {
at = yen.kinds.host;
colmena = # (1)
{ host, ... }:
{
${host.name} = {
deployment.targetHost = host.addr;
imports = [ { networking.hostName = host.name; } ];
};
};
terraform = # (2)
{ host, ... }:
{
resource.cloudflare_record.${host.name} = {
name = host.name;
content = host.addr;
type = "A";
};
};
nixos = b.distributed.nodes { from = yen.kinds.fleet.sinks.nodes; }; # (3)
};
# lb writes into web: its backends know the load balancer
yen.aspects.lb-writes-web = {
at = yen.host.lb;
nodes = # (4)
{ host, ... }:
{
web.networking.extraHosts = "${host.addr} lb.internal";
};
};
yen.aspects.ingress = {
at = yen.cluster.axon;
k8s.ingress.web = {
# (5)
host = "web.example.org";
port = 80;
};
};
yen.aspects.ingress-tls = {
at = yen.cluster.axon;
k8s.ingress.web.tls = true; # (6)
};
}

(1) and (2) give one host’s hive entry and DNS record, read from host.addr. At (3), distributed.nodes reads what other machines wrote for this one. At (4), lb writes into web: its backends know the load balancer. (5) and (6) are two aspects at one cluster. Both write the ingress of axon, and the module system merges them:

result
{
axon = {
ingress = {
web = {
host = "web.example.org";
port = 80;
tls = true;
};
};
};
}

Each battery with the value it is and what it gives:

Battery Value of Gives
deploy.colmena { from; makeHive; meta; } out of a merge sink of { <host> = module; } colmenaHive
deploy.deploy-rs { configurations; hostname; activate; sshUser; } a function, in yen.flake deploy.nodes from built configs
terranix.configuration { from; terranixConfiguration; system; name; } out of a terraform module sink terraformConfigurations.<name>
k8s.manifests { from; lib; output; } out of a sink of manifest modules k8sManifests.<scope name>, merged with the module system
distributed.nodes { from; } a NixOS key the modules other machines wrote for this one
exports.modules { from; output; } out of a module sink at an export scope <output>.<scope name> for other flakes
ContributeCommunitySponsor