Multi-Environment Fleet
What we’re building
Section titled “What we’re building”prodenvironment: one load balancer + two web serversstagingenvironment: one web server- haproxy on the load balancer, configured from sibling web nodes
/etc/hostson every host, listing peers in the same environmentmonitoringauto-injected on all servers vianeededBy
This is a simplified version of templates/fleet-demo.
Traits
Section titled “Traits”# Entity: produces nixosSystemnest.trait.host.class.nixos = select: cfg: nixpkgs.lib.nixosSystem { system = select.node.system; modules = [ cfg ]; };
# Service traits — no output themselvesnest.trait.nginx = { };nest.trait.ssh = { };nest.trait.firewall = { };
# server bundles nginx + ssh + firewallnest.trait.server.needs = [ nest.nginx nest.ssh nest.firewall ];
# lb and web both imply servernest.trait.lb.needs = [ nest.server ];nest.trait.web.needs = [ nest.server ];
# monitoring auto-injects on every server — no node needs to opt innest.trait.monitoring = { };nest.trait.monitoring.neededBy = nest.server;# Namespace attributes inherited by all prod nodesnest.prod.system = "x86_64-linux";nest.prod.env = "prod";
# prod load balancernest.prod.lb-prod = { is = [ nest.host nest.lb ]; addr = "10.0.1.1"; httpPort = 80;};
# prod web serversnest.prod.web-prod-1 = { is = [ nest.host nest.web ]; addr = "10.0.1.10"; httpPort = 80;};nest.prod.web-prod-2 = { is = [ nest.host nest.web ]; addr = "10.0.1.11"; httpPort = 80;};
# staging namespacenest.staging.system = "x86_64-linux";nest.staging.env = "staging";
nest.staging.web-staging = { is = [ nest.host nest.web ]; addr = "10.0.2.10"; httpPort = 80;};nest.rules = [
# Every host: boot config and Nix settings { is = nest.host; nixos = { host, ... }: { networking.hostName = host.name; system.stateVersion = "25.11"; nix.settings.experimental-features = [ "nix-command" "flakes" ]; }; }
# Service traits (auto-injected via server.needs) { is = nest.nginx; nixos.services.nginx.enable = true; } { is = nest.ssh; nixos.services.openssh.enable = true; } { is = nest.firewall; nixos.networking.firewall.enable = true; }
# Monitoring (auto-injected via monitoring.neededBy = server) { is = nest.monitoring; nixos.services.prometheus.exporters.node = { enable = true; enabledCollectors = [ "systemd" ]; }; }
# Load balancer: discover web peers via select.siblings { is = nest.lb; nixos = { select, ... }: let webs = select.siblings nest.web; in { services.haproxy.enable = true; services.haproxy.config = mkHaproxyConfig (map (w: { addr = w.addr; port = w.httpPort; }) webs); }; }
# Every host: /etc/hosts from sibling hosts { is = nest.host; nixos = { select, ... }: let peers = select.siblings nest.host; in { networking.extraHosts = lib.concatMapStringsSep "\n" (p: "${p.addr} ${p.name}") peers; }; }
];How select.siblings scopes environments
Section titled “How select.siblings scopes environments”select.siblings returns all nodes sharing the same __parentPath. Because prod and staging are namespaces (not DOM nodes), all top-level hosts have __parentPath = null — which means they are siblings of each other.
In the fleet-demo, however, the haproxy rule uses select.siblings nest.web. This returns all web-trait nodes with the same parent. Since all hosts share __parentPath = null, this includes web-staging as a backend for lb-prod.
Trait expansion for lb-prod
Section titled “Trait expansion for lb-prod”After step 2 (trait expansion), lb-prod’s is list is:
[ nest.host, nest.lb, nest.server, nest.nginx, nest.ssh, nest.firewall ]After step 3 (neededBy), nest.monitoring is added:
[ nest.host, nest.lb, nest.server, nest.nginx, nest.ssh, nest.firewall, nest.monitoring ]Rules for nginx, ssh, firewall, and monitoring all fire on lb-prod automatically, because it has nest.server via lb.needs.
The haproxy config function
Section titled “The haproxy config function”mkHaproxyConfig = backends: lib.concatStringsSep "\n" ([ "frontend http-in" " bind *:80" " default_backend webservers" "" "backend webservers" " balance roundrobin" ] ++ lib.imap1 (i: b: " server backend${toString i} ${b.addr}:${toString b.port} check" ) backends);select.siblings nest.web is evaluated at nixosSystem build time — if you add a web server, the haproxy config updates without touching any lb rule.
Output
Section titled “Output”After evaluation, byClass.nixos contains:
{ lb-prod = nixosSystem { … haproxy config with 2 (or 3) web backends … }; web-prod-1 = nixosSystem { … nginx, ssh, firewall, monitoring … }; web-prod-2 = nixosSystem { … nginx, ssh, firewall, monitoring … }; web-staging = nixosSystem { … nginx, ssh, firewall, monitoring … };}flake.nixosConfigurations = byClass.nixos makes them available as standard NixOS configurations.