Where is config coming from?

I’m probably still misunderstanding something about the basics of NixOS.
I’ve rewritten most of my config to try to explicitly define the variables I’m using and during that process I’v removed config basically completely.
However I see it often in examples and I wasn’t able to use agenix without config but was able to use it just like this:

{ inputs, config ? { }, ... }:

Edit: This didn’t really work. The reason why it worked, was because I was importing the module the normal way. Meaning without having to inheriting variables. For modules where I’m inheriting variables it still doesn’t work.

Could I add it maybe in a similar way to lib.nixosSystem?

If you mean the config at the top of a nixos module, i.e.:

{config, pkgs, lib, ...}: {
  programs.foo = lib.mkIf config.programs.bar.enable {
    # ...

Then it is a recursive reference to the final config after all modules are evaluated and merged together. One can use it e.g. to define a conditional setting based on whether another option is enabled, like in the example, or for any other purpose that requires self reference to the value of other modules.

I just found out that config doesn’t work for modules which inherit variables.
E.g. in something like this.

{ custom, hostname, inputs, pkgs, ... }:
  imports = [
    (import "${inputs.self}/systems/proxmox-vm" {
      ip = "";
      inherit hostname inputs;
    (import "${inputs.self}/modules/nginx-proxy" {
      domain = "ttrss.2li.ch"; inherit inputs;
    (import "${inputs.self}/modules/restic-server-mysql-client" {
      time = "23:00"; inherit custom hostname inputs pkgs;

I can use config in modules/docker but when I use config in e.g. modules/nginx-proxy I obviously have to inherit it but I don’t where the beginning config is coming from.

I tried add it back but the I got the following because I’ve probably overwritten the real config.

error: attribute '_module' missing

       at /nix/store/vvycl9jb2hy2fgqliw7qy3zq3y1hyicg-source/lib/modules.nix:496:28:

          495|         builtins.addErrorContext (context name)
          496|           (args.${name} or config._module.args.${name})
             |                            ^
          497|       ) (lib.functionArgs f);
(use '--show-trace' to show detailed location information)

I’ve also seen this one, even though I assign a agenix file in the same module.

error: attribute 'age' missing

       at /nix/store/rbc8h3vqqsbq53ss1wc16ai3y2q7c0yz-source/modules/ttrss/default.nix:24:28:

           23|       };
           24|       environmentFiles = [ config.age.secrets.ttrssEnv.path ];
             |                            ^
           25|       ports = [
(use '--show-trace' to show detailed location information)

If you want to keep this pattern of passing arguments to modules explicitly in the imports, it would proably be useful to give those modules two arguments, that way you can keep your custom passed in options and the default nixos module options separate. i.e.

{ my, custom, args }: { config, pkgs, lib, ...}: {
  # my module

If you call such a module like import ./my-module.nix { my = 1; custom = 2; args = 3; } then it will return a lambda where the remaining args will be passed by the NixOS module system itself.


@Nebucatnetzer Calling NixOS modules with custom arguments is an anti-pattern, since it works against the modules builtin composition mechanism of options. I recommend you take a look at this manual section which goes over how NixOS modules look like and how you can write them.

More specifically, e.g. your proxmox-vm module could declare an option services.proxmoxVm.ip and ideally also services.proxmoxVm.enable, then you can use the module like this:

{ inputs, ... }: {
  imports = [ "{inputs.self}/systems/proxmox-vm" ];
  services.proxmoxVm = {
    enable = true;
    ip = "";

Instead of hostname you could use the already existing networking.hostname option. custom being in a module argument is also a bit weird, but I can’t really give any suggestions there without more context.

Main takeaway: Use options whenever possible :slight_smile:


Thanks I give it a try :slight_smile:

I thought that might be the case.
It’s a bit a pity because I really like how it shows which options are getting customized and groups them together with where the actual config lives.
The proxmox-vm is a bit a special example because it contains a lot of hardware settings so it’s not really a service.
The nginx-proxy would fit better for this (see below), however adding options adds a lot of additional code just to change a string.

{ domain, inputs, port ? "8080", ... }: {
  imports = [
  services.nginx = {
    appendHttpConfig = ''
      # Disable embedding as a frame
      add_header X-Frame-Options DENY;
    recommendedProxySettings = true;
    virtualHosts."${domain}" = {
      enableACME = true;
      forceSSL = true;
      locations."/" = {
        proxyPass = "${port}";
        proxyWebsockets = true; # needed if you need to use WebSocket

Edit: If I would rewrite this to options I would make it so that I don’t have to use imports anymore. At the moment I’m using the imports to enable configs on certain systems. So the custom modules get imported on a single line for each system.