There are different “Dendritic” implementations (at least four that I know of) because the Dendritic pattern is not about having a rigid directory/file structure that mandates where to place your files - unlike other configuration libs/frameworks. That’s because Dendritic is more a pattern - the dendritic repo does not even have nix code, it just tries to explain things and maybe it not still that clear, I’ll try to expand here and you tell me if this helps we can add it to the documentation-.
And the pattern is this: Each ./modules/**/*.nix
file is a flake-parts module and each file can contribute to different nix module classes at the same time by using flake-parts-modules , eg:
# modules/terminal.nix -- the important thing here is flake-parts' modules:
{
flake.modules.nixos.myhost = {pkgs, ...}: {
environment.systemPackages = [ pkgs.ghostty ];
};
flake.modules.darwin.myhost = {pkgs, ...}: {
environment.systemPackages = [ pkgs.iterm2 ];
};
}
The important thing here is, instead of having top-level files be an nixos-module
or an nix-darwin-module
or an home-manager-module
, etc. you have a single feature flake-parts module like the one shown above that itself adds configurations to your flake modules.
# modules/vic.nix -- configures os-level vic user
let
userName = "vic"; # a binding just to show another dendritic advantage:
# you use let bindings directly across different module classes, instead of having
# to polute nixos config specialArgs to propagate values accross module classes.
in
{
flake.modules.nixos.myhost = {
users.users.${userName} = { isNormalUser = true; extraGroups = [ "wheel" ]; };
};
flake.modules.darwin.myhost = {
system.primaryUser = userName;
};
flake.modules.homeManager.${userName} =
{ pkgs, lib, ... }:
{
home.username = lib.mkDefault userName;
home.homeDirectory = lib.mkDefault (if pkgs.stdenvNoCC.isDarwin then "/Users/${userName}" else "/home/${userName}");
home.stateVersion = lib.mkDefault "25.05";
};
}
Notice how configuring the os-level user is different if you are on nixos or nix-darwin, but this single vic.nix
file is a cross-cutting concern (the concern of configuring the vic user) that contributes to both and also to an home-manager module. You will also have other files (eg, modules/vic/secrets.nix
) that also contributes to flake.modules.homeManager.vic
but it appends to that module another concern!
That’s the basic idea behind the pattern. How you organize your files is free to you, but since each file is mostly safe contained you can move it around and refactor easily large systems configurations. File location does not matter since they are all loaded by import-tree (or similar).
So each file contributes to some flake.modules.<class>.<name>
of your liking, how you split and organize modules is up to you.
Then you finally import those flake.modules.<class>.<name>
on their respective nixos/darwin configurations (how you wire things up is not defined by the pattern), I for example have a osConfigurations.nix that is itself a flake-module, and it exposes my flake hosts and loads root modules on them.