Skip to content

Context-aware Dendritic Nix

One aspect. Every platform. NixOS, Darwin, home-manager — composed, not duplicated.

Den takes the Dendritic pattern to a whole new level, and I cannot imagine going back.
@adda - Very early Den adopter after using Dendritic flake-parts and Unify. [repo]

I’m super impressed with den so far, I’m excited to try out some new patterns that Unify couldn’t easily do.
@quasigod - Author of Unify dendritic-framework, on adopting Den. [repo]

Massive work you did here!
@drupol - Author of “Flipping the Configuration Matrix” Dendritic blog post.

Thanks for the awesome library and the support for non-flakes… it’s positively brilliant!. I really hope this gets wider adoption.
@vczf - At #den-lib:matrix.org channel.

Den is a playground for some very advanced concepts. I’m convinced that some of its ideas will play a role in future Nix areas. In my opinion there are some raw diamonds in Den.
@Doc-Steve - Author of Dendritic Design Guide

Traditional Nix configurations are host-first. Bluetooth on three machines means three files with duplicated config — one per host, split again across NixOS and home-manager.

# Without Den — duplicated across hosts and module systems
hosts/laptop/configuration.nix # hardware.bluetooth.enable = true
hosts/laptop/home.nix # services.blueman-applet.enable = true
hosts/desktop/configuration.nix # hardware.bluetooth.enable = true (again)
hosts/mac/darwin.nix # homebrew.casks = [ "blueutil" ] (different syntax)

Den inverts this: aspects are the primary unit. One aspect, all platforms.

# With Den — one aspect configures everything
den.aspects.bluetooth = {
nixos.hardware.bluetooth.enable = true;
homeManager.services.blueman-applet.enable = true;
darwin.homebrew.casks = [ "blueutil" ];
};
# Every host that includes it gets bluetooth — across all classes
den.aspects.laptop.includes = [ den.aspects.bluetooth ];
den.aspects.desktop.includes = [ den.aspects.bluetooth ];

Den context args flow automatically — no wrappers, no specialArgs, no infinite recursion:

# Den args and NixOS module args coexist in a single function
den.aspects.laptop.nixos = { host, config, pkgs, ... }: {
networking.hostName = host.name;
};

Den is a library for composing Nix configurations via aspect-oriented programming and a framework for NixOS/Darwin/home-manager built on top.

Under the hood, Den uses a pure algebraic effects pipeline — aspects compile to computations, handlers decide what to do, and a trampoline interprets the result. This gives you extensible policies for entity topology, diagrams of your resolution graph, and custom classes for any Nix module system.

graph LR
  H["host stage {host}"] --> U["user stage {host, user} (for each user)"]
  U --> HM["aspect provides homeManager/hjeim class"]
  H --> OS["aspect provides nixos/darwin class"]
flowchart BT
  subgraph "**den.aspects.dev-tools**"
    n["nixos"]
    h["homeManager"]
    d["darwin"]
  end
  n --> server["server (NixOS)"]
  n --> laptop["laptop (NixOS)"]
  d --> mac["mac (Darwin)"]
  h --> laptop
  h --> mac
  h --> standalone["alice (standalone HM)"]

Aspect-Oriented

Aspects bundle NixOS, Darwin, home-manager, or custom class config in one composable unit. Includes form a DAG. Provides create sub-aspects. One concern, all platforms.

Pure Effects Pipeline

Built on algebraic effects via nix-effects. Aspects compile to computations, handlers own resolution strategy. Policies drive entity topology and bind behavior. Everything is extensible.

No Lock-in

Works with flake-parts, without flake-parts, or without flakes at all. Den works with anything configurable through Nix modules — NixOS, Terraform, NixVim, or your own domain.

Sharable Aspects

Namespaces let you publish and consume aspect libraries across flakes or non-flakes. Diagrams visualize your resolution graph for debugging and documentation.

Built on top of den.lib, Den provides a framework with ready-made facilities for NixOS/nix-Darwin/homes configurations.

flowchart LR
  Schema["den.hosts / den.homes"] --> Pipeline["den.policies pipeline"]
  Aspects["den.aspects"] --> Pipeline
  Batteries["den.provides"] --> Pipeline
  Pipeline --> Resolve["Resolution"]
  Resolve --> Out["nixos / darwin / homeConfigurations"]
  1. Schemaden.hosts and den.homes declare machines, users, and their properties with den.schema modules and extensible freeform types.
  2. Aspectsden.aspects.* bundles per-class configs (nixos, darwin, homeManager, or any custom class) with .includes and .provides forming a DAG.
  3. Resolution pipelineden.policies fan out from entity kinds ({host}{host, user}, {home}) and bind behavior. Batteries register derived entity kinds like hm-host, hm-user, wsl-host.
  4. Resolution — Context-aware dispatch: functions receive entity data automatically based on their argument shape. Class modules can access both Den context and NixOS module args in a single function.
  5. Output — Each host/home is instantiated via nixpkgs.lib.nixosSystem, darwin.lib.darwinSystem, or home-manager.lib.homeManagerConfiguration.
Learn More Extensibility Example: MicroVM

Star History Chart

Contribute Community Sponsor