How to reimport nixpkgs with different configuration inside a module?

I’m trying to write a nixosModule that creates a sonarr nixos-container.
The problem is that sonarr depends on the buildDotNetModule and this has been marked as EOL.
The module then fails to build and I’m looking for a ergonomic way to avoid this error.

This is the simplified flake.nix:

{
  # no inputs
  outputs = {...}: {
     nixosModules.default = { config, lib, ... }: {
        options.mymodule = {
          # ...
        };
        config = lib.mkIf config.mymodule.enable {
          # ...
        };
        imports = [
          ./dir/container.nix
        ];
    };
  };
}

And in ./dir/container.nix:

{lib, config, ...}:
{
  options.mymodule.sonarr = {
    enable = lib.mkEnableOption "sonarr";
	# ...
  };
  config = lib.mkIf config.mymodule.sonarr.enable {
    containers."sonarr" = {
      config = {...}: {
        # ...
        nixpkgs.pkgs = (import config.nixpkgs {
          system = config.system;
          config = config.nixpkgs.config // {
            permittedInsecurePackages = [ "aspnetcore-runtime-wrapped-6.0.36" ];
          };
        }).legacyPackages."${config.system}";
        # ...

I though reusing the config parameter and reimporting nixpkgs would work but this fails with cryptic messages such as:

error:
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:34:12:
           33|
           34|   strict = derivationStrict drvAttrs;
             |            ^
           35|

       … while evaluating derivation 'nixos-vm'
         whose name attribute is located at /nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/pkgs/stdenv/generic/make-derivation.nix:336:7

       … while evaluating attribute 'buildCommand' of derivation 'nixos-vm'
         at /nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/pkgs/build-support/trivial-builders/default.nix:59:17:
           58|         enableParallelBuilding = true;
           59|         inherit buildCommand name;
             |                 ^
           60|         passAsFile = [ "buildCommand" ]

       … while evaluating the option `virtualisation.vmVariant.system.build.toplevel':

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/system/activation/top-level.nix':

       … while evaluating the option `virtualisation.vmVariant.system.systemBuilderArgs':

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/system/activation/activatable-system.nix':

       … while evaluating the option `virtualisation.vmVariant.system.activationScripts.etc.text':

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/system/etc/etc-activation.nix':

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/system/etc/etc.nix':

       … while evaluating the option `virtualisation.vmVariant.environment.etc."nixos-containers/sonarr.conf".source':

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/system/etc/etc.nix':

       … while evaluating the option `virtualisation.vmVariant.environment.etc."nixos-containers/sonarr.conf".text':

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/virtualisation/nixos-containers.nix':

       … while evaluating the option `virtualisation.vmVariant.containers.sonarr.path':

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/virtualisation/nixos-containers.nix':

       … while evaluating the option `virtualisation.vmVariant.containers.sonarr.config':

       … while evaluating the module argument `pkgs' in "/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/services/hardware/bluetooth.nix":

       … while evaluating definitions from `/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source/nixos/modules/virtualisation/nixos-containers.nix':

       (stack trace truncated; use '--show-trace' to show the full, detailed trace)

       error: cannot coerce a set to a string: { buildPlatform = «thunk»; config = { }; crossSystem = null; flake = { setFlakeRegistry = true; setNixPath = true; source = "/nix/store/w66xaybnxjf5zhxapxs3pd3zp7gxk9dw-source"; }; hostPlatform = «thunk»; initialSystem = «thunk»; localSystem = { aesSupport = false; «108 attributes elided» }; «3 attributes elided» }

Maybe I could add a nixpkgs input in the flake and somehow pass it to the ./dir/container.nix module, but I wouldn’t want to add an input if I don’t have to and I always find the ways of passing parameters in modules hacky.
Any ideas how to solve this?

Remove the legacyPackages.whatever bit, that makes no sense in this context. Also I don’t know how you got config.system, it should be pkgs.system aka pkgs.stdenv.hostPlatform.system.

I guess I’m confused because I always see in flakes something like this:

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
 
  outputs = { self, nixpkgs}:
  let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages."${system}";

What I understand from this is that when you want pkgs from nixpkgs you need to append the legacyPackages."${system}" part. Which by this logic leads to me trying to assign nixpkgs.pkgs this way.

Following your tips, if you meant that I had to write this:

{lib, config, pkgs, ...}:
{
  options.mymodule.sonarr = {
    enable = lib.mkEnableOption "sonarr";
	# ...
  };
  config = lib.mkIf config.mymodule.sonarr.enable {

    containers."sonarr" = {
      # ...
      config = {...}: {
        nixpkgs.pkgs = (import config.nixpkgs {
          system = pkgs.stdenv.hostPlatform.system;
          config = config.nixpkgs.config // {
            permittedInsecurePackages = [ "aspnetcore-runtime-wrapped-6.0.36" ];
          };
        });
        # ...
      };
  };
}

well, I’m still getting the same error. Mind that this is the nixpkgs.pkgs of the container not the host.

I’m still learning Nix/NixOS/modules so I don’t know how to proceed here. From my point of view what this is doing is:

  1. taking the config parameter from the outter scope/module argument
  2. getting the nixpkgs from it
  3. reimporting with the right arguments, system and permittedInsecurePackages (I don’t know if this is even possible, it just seemed to be)
  4. assgining that to nixpkgs.pkgs argument of the containers."sonarr".config function

To me, it seemed that it would work, but it doesn’t. Any other insight would be highly appreciated.

Thanks.

Okay, I see what you thought now.
However, legacyPackages is an attribute that is (by convention/schema) generally only defined in flakes.

I suggest using nix repl to explore some of these things.
When you inspect nixpkgs, the flake, directly, it’s an attribute set with some structure as so:

nix-repl> (builtins.getFlake "nixpkgs")
{
  _type = "flake";
  checks = { ... };
  devShells = { ... };
  htmlDocs = { ... };
  inputs = { };
  lastModified = 1738501189;
  lastModifiedDate = "20250202125949";
  legacyPackages = { ... };
  lib = { ... };
  narHash = "sha256-siAWsFAmOcgeMq3BZs0SuBSUhQRDshaGxQ6AzjsQja8=";
  nixosModules = { ... };
  outPath = "/nix/store/ixn33q59i7yw48irb31w46i70630pgnf-source";
  outputs = { ... };
  rev = "a5f6f63ec051391dfe97783833355120e0ec3da1";
  shortRev = "a5f6f63";
  sourceInfo = { ... };
}

However, once you import it, it’s no longer a flake, but a function that takes an attrset (with some config) as input, and in turn returns a set containing all the attributes in nixpkgs. legacyPackages is an attribute of the outputs function in flake.nix, and is not the name of any attribute in nixpkgs:

nix-repl> (import (builtins.getFlake "nixpkgs") {}).legacyPackages
trace: evaluation warning: undeclared Nixpkgs option set: config.allowUnfreePredicate
error: attribute 'legacyPackages' missing
       at «string»:1:1:
            1| (import (builtins.getFlake "nixpkgs") {}).legacyPackages
             | ^

By the way, legacyPackages is only recommended because it has a likelihood to reduce the number of nixpkgs instances (thereby reducing your memory usage during eval and speeding up eval and builds in general), but underneath it’s also just import-ing nixpkgs anyway:

Also, if you’re trying to import nixpkgs, import config.nixpkgs is incorrect. config.nixpkgs is just a bunch of options in the module system, declared via the options.nixpkgs hierarchy. See https://nixos.org/manual/nixos/unstable/#sec-writing-modules.

The import function only takes a string, a path, or an attrset as input.
While config.nixpkgs is in fact an attrset here, it does not have an outPath attribute, so nix is unable to coerce that attrset into a path string to then read its contents. Hence the error you encountered earlier.

What you should do instead is pass in your nixpkgs input (or all inputs) as a module arg, since that flake (like any flake) does have an outPath attribute, as I showed above.

In your nixos configuration flake, the flake.nix should have some line like

  outputs = { nixpkgs, ... }@inputs: {

(The name inputs could be anything, it’s just an informal convention to call it inputs. See https://nix.dev/manual/nix/2.24/language/syntax.html#functions, specifically the bullet on @-patterns, for what this syntax means.)

Then you can pass in inputs (or whatever you decide to call it) via specialArgs arg of nixpkgs.lib.nixosSystem:

  nixosConfigurations.FOOBAR = nixpkgs.lib.nixosSystem {
    specialArgs = { inherit inputs; };
    # other args...
  };

Then in your module, add inputs as a module arg:

{ config, inputs, lib, pkgs, ... }
{
  # ... rest of the module
    imports = [ inputs.nixpkgs.nixosModules.readOnlyPkgs ];
    nixpkgs.pkgs = (import inputs.nixpkgs {
      # rest of the nixpkgs config...
    }); 
  };
}

Thank you for the extensive reply, there is a lot to unpack but will surely try to read every reference you mentioned.

As of now, I can see that the solution you used is what I initially imagined it could it be, using a new inputs.nixpkgs in the top of the flake. But in my case I see an issue with that: I’m writing a nixosModule not a nixosSystem, though one is going to be consumed by the other with the specialArgs argument. So the question is: how to pass a nixpkgs input all the way down to a different file (./dir/container.nix) that is being imported as imports in the flake. i.e.:

{
  #! now with nixpkgs input
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
  outputs = {nixpkgs, ...}: {
     nixosModules.default = { config, lib, ... }: {
        options.mymodule = {
          # ...
        };
        config = lib.mkIf config.mymodule.enable {
          # ...
        };
        imports = [
           #! how to pass it? _module.args? seems hacky
          ./dir/container.nix
        ];
    };
  };
}
{lib, config, ...}: #! how to put in scope here?
{
  options.mymodule.sonarr = {
    enable = lib.mkEnableOption "sonarr";
	# ...
  };
  config = lib.mkIf config.mymodule.sonarr.enable {
    containers."sonarr" = {
      config = {...}: {
        # ...
        #! nixpkgs.pkgs = ???
        # ...

A module will never be evaluated in isolation, but only as part of a configuration.
So you don’t need to add any inputs to the flake.nix that only refers to your new module (I’m assuming it’s in a separate flake from your NixOS config).
You just need to create this inputs binding in the flake.nix that defines your NixOS configuration.

On a side note, now that I think about it, there’s also another option that would be less code and wouldn’t even require passing around inputs: use import pkgs.path instead of import inputs.nixpkgs. And the imports entry would become

imports = [ "${modulesPath}/misc/nixpkgs/read-only.nix" ];

My goal was that to a nixosSystem consume this nixosModule it would only need to to add

{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
  inputs.mymodule.url = "someurl";
  outputs = {nixpkgs, mymodule, ...} : {
    nixosConfigurations.FOOBAR = nixpkgs.lib.nixosSystem {
      modules = [
          mymodule.nixosModules.default               # module added
          ({...}: { mymodule.enable = true; })        # module enabled
          # other user provided modules
          # ./users/module.nix
      ];
    };
  };
}

In the end the user will have to add a specialArgs? As the developer of nixosModule I’m unable to reduce the user’s overhead? The user will have to provide a nixpkgs instance configured to allow insecure packages?

lol I tried this and still get the insecure packages error

flake.nix

  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";

  outputs = {nixpkgs, ...}: let
    sonarrPkgs = import nixpkgs {
          system = "x86_64-linux";
          config = {
            permittedInsecurePackages = [ "aspnetcore-runtime-wrapped-6.0.36" ];
          };
        };
  in {

    nixosModules.default =
      { config, lib, ... }: {
       # ...
        imports = [
          ./dir/container.nix { _module.args = { inherit sonarrPkgs; }; }
        ];


./dir/container.nix

{lib, config, pkgs, sonarrPkgs, ...}:
    # ...
    containers."sonarr" = {
      config = {...}: {
        nixpkgs.pkgs = sonarrPkgs;
        services.sonarr = {
          enable = true;
          package = sonarrPkgs.sonarr;
        };
      };

By this time I have no idea what to do and I’m actually getting tired of such convoluted system

No, I just gave another option to use pkgs.path and modulesPath.

And what is this error? Do not paraphrase it.

The issue is that I need to allow an insecure package to evaluate also.

This is the error:

       error: Package ‘dotnet-sdk-6.0.428’ in /nix/store/p6ccgjymrlv4wwnxkx9hbhz0rbd5mnlr-source/pkgs/development/compilers/dotnet/build-dotnet.nix:222 is marked as insecure, refusing to evaluate.


       Known issues:
        - Dotnet SDK 6.0.428 is EOL, please use 8.0 (LTS) or 9.0 (Current)

       You can install it anyway by allowing this package, using the
       following methods:

       a) To temporarily allow all insecure packages, you can use an environment
          variable for a single invocation of the nix tools:

            $ export NIXPKGS_ALLOW_INSECURE=1

          Note: When using `nix shell`, `nix build`, `nix develop`, etc with a flake,
                then pass `--impure` in order to allow use of environment variables.

       b) for `nixos-rebuild` you can add ‘dotnet-sdk-6.0.428’ to
          `nixpkgs.config.permittedInsecurePackages` in the configuration.nix,
          like so:

            {
              nixpkgs.config.permittedInsecurePackages = [
                "dotnet-sdk-6.0.428"
              ];
            }

       c) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add
          ‘dotnet-sdk-6.0.428’ to `permittedInsecurePackages` in
          ~/.config/nixpkgs/config.nix, like so:

            {
              permittedInsecurePackages = [
                "dotnet-sdk-6.0.428"
              ];
            }

Then you need to add that string to the list as well.

Ok so apparently this time was because it showing other packages I had to allow, not the same error. And it finally built!

Yes, indeed this time was my fault. Thank you very much for the help, kind stranger, you were very helpful even though I was running out of hope. I have one last question: If you had to pass arround a new nixpkgs instance so some package in other module could be built, would you use the _module.args approach? Do you see any benefits or drawback in it?

Also, how would I refer the consumer’s system instead of hardcoding it:

  outputs = {nixpkgs, ...} @ inputs: let
    sonarrPkgs = import nixpkgs {
          system = "x86_64-linux";

what is the proper variable in inputs to use it?