An inventory-driven NixOS flake architecture for assembling multi-host systems from reusable modules, scoped facts, validation, and generated deployment outputs.
This started as an attempt to organize my NixOS systems around reusable feature branches. It turned into something more useful: a small assembly system where the root flake stays tiny, inventory is the source of truth, reusable modules stay reusable, and the library layer builds the final machines.
The basic idea:
inventory = what exists and what each host wants
dendrites = reusable NixOS behavior
fruits = named deployable outcomes
homes = reusable Home Manager behavior
hosts = machine-specific exceptions
lib = assembly, validation, dependency resolution, and output generation
A host does not need to manually import every piece it depends on.
Instead, a host says:
I am this kind of machine.
I have these users.
I belong to these networks.
I need these capabilities.
Here are my hardware facts.
Here are my deployment hints.
Then the flake assembles the final NixOS and Home Manager outputs from that model.
This is not a polished framework. It is a real architecture extracted from my own homelab / workstation setup, cleaned up into a public mirror.
A lot of multi-machine NixOS flakes start clean, then slowly collapse into a junk drawer.
At first, the root flake.nix is readable. Then you add:
- another host
- Home Manager
- shared users
- roles
- storage
- private networking
- deployment tooling
- host-specific hardware weirdness
- one-off service exceptions
Eventually the root flake starts carrying too much meaning.
This repo tries to avoid that by separating the parts that usually get mixed together.
nix-arbor separates four things:
Facts are things that are true about the world.
Examples:
- hostnames
- users
- roles
- networks
- ports
- storage devices
- ZFS pool names
- tape device paths
- deployment targets
- bootstrap hints
These live in inventory/.
Behavior is reusable NixOS or Home Manager code.
Examples:
- base system setup
- desktop behavior
- ZFS support
- tape support
- private overlay networking
- gaming/workstation setup
- service configuration
These live in dendrites/, homes/, and fruits/.
Assembly is the logic that turns facts plus behavior into final systems.
This lives in lib/.
The assembly layer handles things like:
- registry discovery
- inventory normalization
- dependency resolution
- host module assembly
- Home Manager assembly
- validation
- generated deployment outputs
Overrides are the weird machine-specific exceptions that should not pollute reusable modules.
These live in hosts/.
A reusable feature should become a dendrite. A weird one-machine fix should become a host override.
.
├── flake.nix
├── inventory/
│ ├── hosts.nix
│ ├── users.nix
│ ├── roles.nix
│ ├── networks.nix
│ └── ...
├── lib/
│ ├── assembly.nix
│ ├── registries.nix
│ ├── validation.nix
│ ├── deployments.nix
│ └── ...
├── modules/
│ └── flake-parts/
├── dendrites/
├── fruits/
├── homes/
├── hosts/
├── checks/
└── docs/
inventory/ is the source of truth.
It describes what exists, what hosts want, what networks exist, which users exist, which facts are true, and what deployment hints are available.
The point is not to make inventory tiny. The point is to put information where it belongs.
A host can be data-heavy as long as it stays behavior-light.
dendrites/ are reusable NixOS capability branches.
They are intentionally close to normal NixOS modules. The difference is that they follow a small convention so they can be discovered, described, selected, validated, and assembled.
Examples:
dendrites/base/
dendrites/desktop/
dendrites/storage/
dendrites/storage/dendrites/zfs/
dendrites/storage/dendrites/tape/
dendrites/network/dendrites/yggdrasil-private/
A dendrite should define reusable behavior. It should not need to know every host that may use it.
Leaves are small internal modules owned by a dendrite.
They are implementation details, not global selections.
This keeps a branch organized without making every helper file part of the public assembly surface.
fruits/ are named deployable outcomes.
A fruit is a service, appliance, persistent app, or higher-level thing that a host can run.
A fruit can require dendrites. For example, a tape-management fruit can require tape-related storage behavior.
homes/ contains reusable Home Manager behavior.
This lets users and roles select Home Manager pieces through inventory instead of wiring everything by hand.
hosts/ contains host-specific overrides.
This is where machine-specific weirdness goes when it should not become a reusable module.
The root flake.nix is not the brain of the system.
It mainly defines inputs and routes into flake-parts.
The actual meaning of the system lives in inventory, reusable behavior, and the library layer.
The repo tries hard to avoid redundant or misplaced information.
A service module should not need to know every host IP.
A host should not need to manually import every transitive dependency.
Network facts should live with network inventory.
Host facts should live with host inventory.
Reusable behavior should live in reusable modules.
The library stitches the pieces together.
The usual flow is:
1. add a new dendrite
2. describe it with metadata
3. select it in inventory for the hosts that need it
4. build
You do not need to edit the root flake every time you add a new reusable capability.
The library layer can resolve selected dendrites, fruits, required dependencies, users, Home Manager modules, host overrides, and generated outputs.
That means host definitions can stay focused on intent and facts instead of implementation details.
The flake validates the model before deployment.
Current validation checks include things like:
- unknown users
- unknown roles
- unknown networks
- duplicate ports
- invalid tape managers
- conflicting dendrites
- missing ZFS facts
- missing tape device facts
- missing required fruits
- invalid private overlay network references
- invalid deployment/bootstrap references
This is one of the most important parts of the architecture.
The point is to fail early with a useful error instead of letting mistakes turn into confusing deployment failures.
The same inventory can generate:
nixosConfigurationshomeConfigurations- Colmena output
- deploy-rs output
That keeps deployment metadata close to the host model instead of creating a second hand-maintained source of truth.
This pattern is useful for:
- multi-host NixOS setups
- homelabs
- workstations plus servers
- storage-heavy systems
- private overlay networks
- generated deployment targets
- shared Home Manager setups
- systems with a lot of hardware-specific facts
- configs that need to grow without becoming unreadable
It is especially useful when you want many hosts to share behavior without copying the same information everywhere.
This is not a beginner NixOS template.
It is not a polished framework.
It is not a secrets-management solution.
The private repo this came from currently has ugly secret handling because I was focused first on the hardware, tape integration, deployment surfaces, and assembly model. That part needs to be cleaned up properly with a real secrets system.
This public repo is mainly about the architecture.
This repo still uses the word dendrites for reusable capability branches because that language fits the tree model well.
It should not be treated as a strict implementation of any existing public “dendritic NixOS” pattern.
A better description is:
inventory-driven NixOS host assembly
or:
a tree-shaped NixOS flake architecture with scoped inventory and reusable behavior branches
This repository is generated from a private source repo.
That means:
- secrets are removed
- sensitive deployment details are removed or replaced
- some example values are synthetic
- private-only experiments may be omitted
- some rough edges are still visible because this comes from a real working setup
It is currently running on my own hardware across two machines, and I am expanding it further.
Read these first:
docs/architecture.mddocs/dendritic-guide.mddocs/authoring-guide.mdexamples/demo-inventory/README.md
Quick commands:
nix flake show
nix flake checkActive, practical, and still evolving.
Some of it is clean. Some of it is experimental. Some of it is there because real machines needed it.
The main value is the structure:
tiny root flake
inventory as source of truth
reusable behavior branches
assembly logic in lib
early validation
generated deployment surfaces
controlled information scope
That is the part worth sharing.