caisson

The foundation framework for composable Nix flakes.

A caisson is sunk to bedrock, sealed, and becomes the foundation: the part of the bridge no one sees, and the part everything else stands on. caisson can provide that structure for your flake: closed inputs, disciplined library composition, and module classes, based upon flake.parts.

Get started Read the docs

Closed inputs

Modules and overlays close over your flake's inputs, not your consumer's. Downstream users are not required to re-declare your internal dependencies, and can still override any input through standard flake mechanisms.

# a module, closed over the
# defining flake's inputs
{ closure-inputs, ... }:
{ ... }:
{
  imports = [
    closure-inputs.some-helper.modules.flake.default
  ];
  # consumers are not required to declare
  # their own dependency on some-helper
}

Library overlays

Flakes should export their logic under lib.<namespace>: namespacing instead of a fight over the global namespace. Overlays declare their dependencies explicitly and compose in dependency order, so layered abstractions stay predictable.

# lib-overlays/default/default.nix
{ closure-inputs, ... }:
{
  imports = [
    closure-inputs.other-flake.libOverlays.default
  ];
  overlay = final: prev: {
    my-flake = (prev.my-flake or { }) // {
      greet = name: "hello, ${name}";
    };
  };
}

Module classes

Modules register under a class key naming the module system they belong to: flake for flake-parts modules. The class keeps a module from being imported into an evaluation that can't understand it; each shipped integration registers its own class for its module ecosystem.

modules = lib: {
  flake = {
    default = lib.caisson.mkFlakeModule
      ./modules/flake-parts/default;
  };
};
# exportable to consumers,
# closed over your inputs

Get started

  1. Add the input. inputs.caisson.url = "github:nix-caisson/caisson";
  2. Compose your library. caisson-core.mkLib merges your overlays with caisson's into your flake's lib.
  3. Emit your outputs. mkFlake evaluates your config module and module selections into flake outputs.
# flake.nix
{
  inputs.caisson.url = "github:nix-caisson/caisson";

  outputs = inputs@{ caisson, ... }:
    let
      lib = caisson.lib.caisson-core.mkLib {
        inherit inputs;
        projects = { inherit caisson; };
        libOverlays = mkLibOverlay: {
          default = mkLibOverlay ./lib-overlays/default;
        };
      };
    in lib.caisson.mkFlake {
      configModule =
        lib.caisson.mkFlakeModule ./configs/flake-parts/my-flake;
    };
}

The repository's examples/literate-flake walks the whole structure with commentary; the docs explain each concept (closed inputs, module classes, library overlays, and the library lifecycle) with the reasoning behind the design, and the layout conventions cover how a caisson repository is arranged.

Integrations

caisson ships integrations that carry the same conventions (closed inputs, library overlays, class-keyed modules) into other module ecosystems, each registering its own module class. flake-parts itself is one of them:

Integrationbrings caisson's conventions to…
flake-partsflake outputs: mkFlake, plus exporting modules and overlays
nixpkgsnixpkgs package sets and overlays
nixosNixOS configurations
home-managerHome Manager configurations
terranixTerranix / Terraform configurations
colmenaColmena deployment hives
system-managersystem-manager configurations on foreign distros

Each integration is a library overlay exported by this flake, also consumable as keyed entries through caisson-core directly. Integrations take their ecosystem as an explicit ecosystemSrc argument and pin nothing themselves.