One graph for the OS and everything under it
NixOS made your hosts reproducible. The hypervisors, private network, and backup servers beneath them stayed manual. NXD closes that gap: one Nix-authored description of the whole environment, planned as one dependency-ordered graph, applied only against a digest you approved.
$ nxd switch medo --source ".#nxdConfigurations.lamt"
evaluating selected target outputs...
evaluated selected target outputs in 4s
approval required for switch plan .nxd/plans/switch-4592a2f32894.plan.json
plan digest: sha256:4592a2f32894b465309c87f2052ab9b97ee19799898d1cfc5ec5b69cb446a3ea (3 actions, 1 target)
PLAN ACTIONS:
1. deployment-target/medo build -> nixos-rebuild switch [ServiceImpacting]
2. secret/hosts/medo/ssh-host stage SOPS decrypted key [IdentityCritical]
3. deployment-target/medo verify active system closure [ReadOnly]
✓ [medo] build placement configured=auto builder=deploy@utils
✓ [medo] realized closure /nix/store/jpz7lcmwjzjymdr38kxh428rwa2arr04-nixos-system-medo
✓ [medo] active system verified: desiredSystemPath == activeSystemPath (delta +0)
How a change reaches a host
01 Evaluate
nxd.lib.evalConfiguration reads your Nix modules and emits a canonical JSON specification. Offline: no endpoint is contacted, no secret is decrypted.
02 Plan
nxd plan computes a dependency-ordered action graph across every provider, classifying each action by risk from ReadOnly up to IdentityCritical.
03 Approve
The plan is persisted and hashed. Applying requires approval of that exact digest, so the plan you reviewed is provably the plan that runs.
04 Apply & verify
Typed Rust providers mutate, then live state is re-read. Success means desiredSystemPath == activeSystemPath, not a zero exit code.
Why it is built this way
Ordering that crosses providers
A Tailscale auth key must exist before the guest that consumes it boots. That is an edge in the graph, not a step in a runbook — one scheduler orders identity, network, hypervisor, and OS together.
Plans are artifacts with identities
Approval binds to a plan digest, not a session. If configuration changes between planning and applying, the digest stops matching and the apply refuses rather than executing something you never read.
Secrets never reach the store
The Nix store is world-readable. Configuration carries binding references only; SOPS/age resolves them at apply time into memory or 0600 staging. Git holds ciphertext, the store holds nothing sensitive.
One transport, pinned and multiplexed
Every provider shares nxd-transport, which enforces exact Ed25519 host key pinning with ControlMaster multiplexing by default. Host key verification and connection reuse are one decision, enforced in one place.
Build placement is a decision
Closures build on a builder, the orchestrator, or the target, resolved per host. A low-memory node the orchestrator cannot realize for gets the reviewed derivation realized in its own mounted store instead.
Machine-readable end to end
Canonical JSON specs, --format json on plan and verify, typed provider contracts, explicit postconditions. Agents inspect real state and get empirical confirmation instead of parsing shell output.
Providers
| Provider | Resource kinds | Transport | Maturity |
|---|---|---|---|
| Nix | deploymentTarget, nixSystem | SSH multiplex, Nix remote build | Active |
| Proxmox VE | pveCluster, pveVm, pvePxeAsset | PVE REST, QDevice over SSH | Active |
| Proxmox Backup | pbsAppliance, pbsDatastore, pbsUser | PBS REST | Active |
| Tailscale / Headscale | headscaleUser, headscaleNode, preauthKey | Self-hosted Headscale control plane | Active |
| Identity | sshHostIdentity, publicArtifact | SOPS/age key sink | Active |
| VMware | vmwareVm | Fusion / ESXi VMX substrate | Active |
| DigitalOcean | digitalOceanDroplet | DigitalOcean REST | Early |
| WSL | wslDistribution | wsl.exe lifecycle | Early |
Common invocations
Run NXD from your consumer repository against a pinned revision:
# Inspect the action graph before anything moves
nxd plan medo --source ".#nxdConfigurations.lamt"
# Switch the system closure on a live host
nxd switch medo --source ".#nxdConfigurations.lamt"
# Re-enroll into the tailnet, overriding the endpoint out of band
nxd switch medo=203.0.113.48 --reenroll --source ".#nxdConfigurations.lamt"
# Machine-readable output for agents and CI
nxd plan medo --source ".#nxdConfigurations.lamt" --format json
NXD is pre-release and under active development. It also runs the homelab it was built for. Star the repository to follow along, or read the technical manual for the full architecture.