Posted on

Merging configurations with Nix

Here's a problem: How to split up my Nix config?

I would like to split my config in multiple files, but:

  1. I want the individual files to be as stand-alone as possible, so I can copy them between projects, or give to friends
  2. I want to edit system config from those stand-alone units

This is a real problem without Nix that is usually dealt with by using something like Bash >> operator to append text to the end of a config file and hope for the best. Or if your config files are YAML, you have to resort to jq or regex-fu.

How does Nix help us?

Lets imagine a scenario where I have a config like this:

# configuration.nix
{ pkgs, ... }:
{
  # Some service definitions
  services.abc = { ... };
  services.xyz = { ... };

  # Networking
  networking.firewall = {
    enable = true;
    allowedTCPPorts = [
      3000 # Service ABC
      3001 # Service XYZ
    ];
  };
}

Now I would like to split this by extracting services into their own files:

# configuration.nix
{ pkgs, ... }:
{
  imports = [
    ./services/abc/default.nix
    ./services/xyz/default.nix
  ];

  # Networking
  networking.firewall = {
    enable = true;
  };
}
# services/abc/default.nix
{ pkgs, ... }:
{
  services.abc = { ... };

  networking.firewall.allowedTCPPorts = [
    3000 # Service ABC
  ];
}
# services/xyz/default.nix
{ pkgs, ... }:
{
  services.xyz = { ... };

  networking.firewall.allowedTCPPorts = [
    3001 # Service XYZ
  ];
}

This is very nice because now I can create my separate modules and Nix will take care of merging the configurations, in this case by concatenating the networking.firewall.allowedTCPPorts list from the two modules. This option is defined here and the magical bit is that, by default, lists are merged using concatenation and additionally the apply function canonicalizePortList is applied to make sure each port only appears once.

The documentation for this is Extensible Option Types and basically what happens is that modules options are declared using types in such a way that also defines how these are supposed to be merged.