The dendritic pattern

Wow you pushed it even further, nice !!!

2 Likes

Update 2026-05-18: I’m rediscovering the dendritic pattern and this thread after one year. I’ve already converted every single project I actively work on to flake-parts and import-tree. Now I’m digging further, and my two primary observations: 1) it seems that the dendritic pattern has gathered a thriving research community, denful, and 2) dendritic exceeds flakes: it seems that more and more of flakes semantics are entirely expressible without enabling flakes.)

That makes a lot of sense; since a flake-parts module can define any number of flake outputs, it can also address things that link two kinds of outputs that aren’t of the same kind. This is not so easy with plain nixosModules.

I don’t wish to complicate my system configuration(s) with this pattern for now.

I think that a good configuration is modular in the sense that someone can take any one file of mine, and move it into their configuration, and it will more or less work. That only works if their configuration is made of the same kind of modules as mine, i.e. nixosModules.

In short: flake-parts modules blows the complexity budget for newcomers, and I’d like if my NixOS configuration is readable and adaptable by newcomers, since this is the most valuable personal project you can have for getting onboarded into the ecosystem.

Still, this pattern is very cool. I’d definitely consider using it for Nix-heavy projects, and it is very nice to know there’s an example of it here, so thanks a lot for it.

3 Likes

Just a suggestion for anyone who wants to collaborate on the dendritic pattern to set up a “watch” for any of PRs, discussion and issues.

back when I tried to use flake-parts in my multi-host config flake (wasn’t aware of the dendritic pattern then though), I eventually gave up because I failed to figure any way to configure nixpkgs differently per-host (basically I want to enable CUDA only where there is hardware that needs it, because enabling it universally causes too many pointless rebuilds). either I misunderstand the perSystem mechanism, or I missed a way to bypass it, or it’s just a fundamental limitation?

NixOS configurations are not typically defined under perSystem. So their pkgs don’t come from perSystem. So I’m pretty sure the dendritic pattern can support arbitrary pkgs per host, although I’ve always used the defaults.

I think the main problem is that it is not clear when modular configuration of nixpkgs works and when it doesn’t (for me it never did).

I actually do configure Nixpkgs within my NixOS configs. For the sake of unfree packages. Seems trivial:

apparently it was residual trauma from bug: Flake config cannot use unfree packages despite nixpkgs.config.allowUnfree = true · Issue #2942 · nix-community/home-manager · GitHub, sorry for the noise!

2 Likes

Just wanted to share that I’ve seen more people adopting the Dendritic pattern :).

I currently have a list of Dendritic Repositories as part of Dennix documentation.

If you want yours listed there, send a PR. Dennix will try to generate documentation on which flake modules and nix classes you use. (see previous link).

I also added support for dendritic repos using the Unify library. And I see at least a couple of them working now.

Anyways, I’m quite happy to find more people adopting this pattern.

:slight_smile:

2 Likes

@vic a cool example on the dendritic example would be how to take a 2 host small example (I like your SSH + some other demo) → turn it into denditric.

2 Likes

yep, I still have (still on my TODO) to contribute a complete dendritic example - or perhaps a basic template - to the dendritic repo. I agree that having something that shows how to setup up a couple of hosts sharing features between them can shine some light on using the pattern. I’ve been -sadly dealing with loss/health issues past month- busy mainly documenting things on vic/dendrix repo (added some documentation there about why I believe, the dendritic pattern is particularly well suited for community-driven configs).

But yes, I’ll try to remember to contribute a better example to the dendritic repo.

1 Like

Today I added a flakeModules.allfollow after a request to make allfollow opt-in in flake-file.

And how cool it is that including a flake module now also adds features to your own flake. In this case, importing flakeModules.allfollow enables automatic pruning of flake.lock whenever it gets created.

If you are using dendritic setups, give flake-file a try. dependencies get closer to where they are used. for example, my doom emacs config defines dependencies right there where they are being used.

3 Likes

As I’m beginning to adopt flake-parts, I’m gravitating towards a similar idea.

I’m not making every file a flake-parts module, though. The key, in my mind, is to make flake-parts reach the leaves of the source tree everywhere, so that anything that needs to be done can be done sufficiently locally to the code that needs it.

For me, this means it’s good enough to have most nix files be flake-parts modules, and, crucially, the entry point to every directory be a flake-parts module. I do make a few exceptions, which I name consistently with their format, and always have in their own directory, accessing nothing outside that directory, with a flake-parts module as an entry point dedicated solely to that object:

  • package.nix: A callPackage-able file
  • overlay.nix: A raw overlay function in a file

This makes comparatively isolated components of your flake more understandable and re-useable, both for non-flake users and non-flake-parts users, and doesn’t really hurt co-location ability, in my opinion.

In a way, you could consider the entire directory these files are in essentially “a flake parts module with attachments.”

Also justifiable exceptions:

  • default.nix: nix-build entry points, if you still want to be able to use that.
  • shell.nix: nix-shell entry points, if you still want to be able to use that.
  • flake.nix: if you use subflakes for some reason (usually a bad idea, but there are reasons), or you include a flake template.
1 Like

sounds good!, having flake-parts modules be the entry-point of leaf directories, makes sense, and also allows you to have other nix files that not necessarily are flake-parts.

<offtopic rel=“importing”>

how do you load these flake-parts ? if, you have for example a flake-parts module /module.nix as entrypoint on some directories, you can use import-tree to load them with something like this:

import-tree.filter (lib.hasSuffix "/module.nix") ./root

and only those module.nix are auto-loaded, everything else can be callPackage’d or loaded in a different context.

for example if you had many packages each having a package.nix file, you can callPackage all of them at once (if you wanted) with the following function:

root: callArgs: lib.pipe import-tree [
  (i: i.filter (lib.hasSuffix "/package.nix"))
  (i: i.map (file: pkgs.callPackage file callArgs))
  (i: i.withLib lib)
  (i: i.leafs root)
]

or use something like @drupol’s pkgs-by-name-for-flake-parts from the parent module.nix

</offtopic>

be sure to share how things worked better for you.

I have a flake-parts entry point in every directory, and it’s responsible for importing the modules in that directory (other than itself), and the entry points of any subdirectories. I pass a version of my lib in as a specialArg to make it available to do this easily. My flake’s a refactoring mess right now, but you can see the core implementation here:

I have some more variants that do things like filterAttrs the result to exclude certain files (like the entry point), and call attrValues on it to get a list for imports.

Designing it this way means I still have the option to cut off flake-parts from some subdirectory if I want to handle it differently for some reason, and I can do this cut-off locally.

Currently my entry points are named default.nix, though I think I’ll change that soon, since it conflicts with the nix-build entry point, which I might still want to use, and I don’t need the notational convenience of importing directories directly when my importing is all managed by lib functions anyway.

I just defined a flake-parts option called pkgs-overlays, which get accumulated into _module.args.pkgs so that all of my flake outputs use it automatically. Usage example: my workarounds directory

I’d like to add to that: hardware-configuration.nix (though I name them by hostname). I have a directory with hardware configs for each machine I have a NixOS config for. Most recently I’ve just renamed that to modules/hardware/_generated (so that import-tree ignores it).

I want to be able to do nixos-generate-config --show-hardware-config > some-hostname.nix and not need to wrap it in flake.modules.nixos.hardware-for-some-hostname or something.

And also for my usecase (generating an ISO which runs disko automatically) I have a file called _disko.nix next to the NixOS config loading it. This file takes an argument device and thus cannot be used with flake-parts I don’t think.

1 Like

I usually just write:

{
  perSystem = { pkgs, lib, ... }: {
    legacyPackages = lib.packagesFromDirectoryRecursive {
      inherit (pkgs) callPackage newScope;
      directory = ./pkgs/by-name;
    };
  };
}

This works for both flake-parts and non-flake parts (if you remove the perSystem and add something that maps it to multiple systems in some other way). If you wrap it with { pkgs ? import <nixpkgs> {}, ... }: let inherit (pkgs) lib; in instead, you get a default.nix. It’s the simplest/most vanilla way I think.

1 Like

There will be a two hours dendritic pattern workshop in NixCon! If you want to get and or give support, get started, improve, refine or solve a problem, consider showing up! I’ll be there to help and probably learn a few things from you, as well.

9 Likes

Because there’s not one.

I’ve had the same issue, if you ask about flakeModules, others will tell you about flake.modules, or assume your asking about generic modules or flake-parts modules.

flakeModules are actually broken right now, as far as I can tell?

I think we should come up with a term other than modules because it can reference

nixpkgs modules

myModule = {…}: {};

modules = [ ];

imports = [ ];

nixos, Darwin, home-manager, and "generic"

Now: for flake-parts modules???

Sidenote: of all of the flake-parts modules.${name} type modules…

AKA:**standard**/NOT flakeModule -

modules of flake-parts

Generic is the best, most widely reusable type, which can be reused in ALL of the above configurations… leading to the cleanest, most useful module code you could have, and most projects should only use these…

So, of course…

  • there’s no toplevel ouptut for them
  • no frameworks to merge them in fancy ways like all the others get
  • no tooling to define them rapidly
  • it’s only documentation reference - refers to a nixos module instead of the generic type… and no one is using them, or knows what they are…

Those are part of the “extra“ modules under

inputs.flake-parts.flakeModules.modules

Now… as for

flake-parts flakeModules.

those are imported through

inputs.flake-parts.flakeModules.flakeModules

and they’re a whole different level of unnecessarily confusing…

Not because they’re all that confusing in concept, but for a reason about as stupid as why no one uses generic modules.

Because almost one realizes they exist, and instead takes every other method you could possibly imagine…

trying to get the results of using them, and eventually “usually” gets annoyed, creates a function, and uses some form of anti-pattern in order to push their lib into your flake

Some create a local fixed point, and override something inside of it, or creates a literally pointless function…

that does nothing other than serve to set args and defer evaluation, so they can make your flake evaluate it instead, when we have types for that?…

Anyhow, they’ll do that, to push their lib into the consumer flake, because in current state they may not even be able to use flakeModules for the problem they were created to solve…

even if they’re technically accessible, the lack of reasonable, understandable docs on its usage is a problem, and they are “shadowed/broken” by early evaluation, and get blamed for everything else, when they’ve literally never been the problem.

see my linked issue about it here:

inputs.flake-parts.flakeModules.flakeModules

In reading several of your posts I have lost track of your reasoning early on. So I think you have some confusion. Would you be interested in a 1-on-1 with me where I would do my best to understand your complaints regarding flakeModules?

1 Like