Hedronite Lesson · Polyglot-Dev / Nix · Mon 2026-09-21

Nixpkgs Parameters — Pill 16

Import nixpkgs as a function. Pass system for the arch. Pass config for repository policy.

Lesson Class: Asr (Nix language track)
Focus: import · system · config · NIXPKGS_CONFIG · laziness
Done-criteria: import { system; config; }; say config is nixpkgs, not Nix-the-language
Grounding: on-disk nix-pills.epub Pill 16 · live nixos.org canonical
Note: Not Pill 15 · not flakes · not install · next session 12 Pills 17
Target Select
Pass system so pkgs builds for the named arch.
Policy Gate
config (and ~/.config/nixpkgs/config.nix) lives in nixpkgs.
One call
nix-build auto-calls a function once, not twice.
config is a nixpkgs parameter. It is not part of Nix-the-language.

<!-- hal:authoritative:yaml -->

*Import nixpkgs as a function. Pass system for the build target. Pass config for repository policy. Keep those two names separate from Nix-the-language.*

§I — Frame

Asr session 11. Eleventh live fire of the Nix language track. Week 4 fire 1. The page is Nix Pills Nixpkgs Parameters, Pill 16. Luca Bruno wrote the series. License CC BY-SA 4.0. Ground from the on-disk EPUB at . Live URL is canonical if the EPUB drifts: https://nixos.org/guides/nix-pills/16-nixpkgs-parameters.html.

Session 10 was Pill 14. You learned .override and why makeOverridable must return override again. That folder stays closed for re-teaching. This session opens the repository itself: <nixpkgs> is not a bag of packages sitting on disk as a finished set. It is a function. You call it. The interesting arguments are system and config.

Done-criteria from the syllabus: you can import <nixpkgs> { system = …; config = …; } and say aloud that config belongs to nixpkgs, not to Nix-the-language.

Not Pill 15 (NIX_PATH; dropped). Not flakes. Not install. Not Python wrapping. Launch a terminal when you have Nix. If Nix is not on the machine today, read the nix-repl numbers from the Pill and from this lesson.

§II — default.nix is a gate, then a function

nixpkgs default.nix does a narrow job. It checks that the Nix version is new enough, then imports pkgs/top-level/all-packages.nix. From that import forward, the Pill names the result pkgs.

all-packages.nix is a function. Its interesting parameters:

  1. system — defaults to the current system.
  2. config — defaults to null.
  3. others the Pill leaves unnamed for this lesson.

system selects the architecture the packages will be built for. config is an attribute set packages may read to change derivation behavior. Those two knobs are the whole of this session.

§III — The system parameter (named technique: Target Select)

Yagyu names the cut once. Target Select: pass system when you import pkgs so every downstream attribute builds for that arch.

Release expressions often look like:

{ system ? builtins.currentSystem }:

let
  pkgs = import <nixpkgs> { inherit system; };
in
  ...

Why inherit? Because pkgs accepts system, and the outer release expression also accepts system. One value travels down. Without that pass-through, a release pinned to i686 can still import an x86_64 pkgs by accident.

Concrete build:

nix-build -A psmisc --argstr system i686-linux

That builds psmisc for i686-linux on an amd64 host. The Pill compares it to Debian multi-arch. Cross-compiling also lives in nixpkgs; Pill 16 marks it as out of scope for the short walk.

If-then-thus: if you need a 32-bit package set on a 64-bit machine, then pass --argstr system i686-linux (or inherit system into import <nixpkgs>), thus the selected attribute realizes for that target instead of builtins.currentSystem.

§IV — The config parameter (named technique: Policy Gate)

~/.config/nixpkgs/config.nix is not hardcoded in Nix-the-language. It is nixpkgs policy loading.

Resolution order from the Pill:

  1. Caller passes config = { … }; to import <nixpkgs> { … }.
  2. Else config is null, so nixpkgs reads the NIXPKGS_CONFIG environment variable.
  3. Else it picks $HOME/.config/nixpkgs/config.nix (older docs said ~/.nixpkgs/config.nix).

After the file is chosen, it is imported as a Nix expression. That value becomes config for the repository.

Readable in the repl:

$ nix repl
nix-repl> pkgs = import <nixpkgs> {}
nix-repl> pkgs.config
{ }
nix-repl> pkgs = import <nixpkgs> { config = { foo = "bar"; }; }
nix-repl> pkgs.config
{ foo = "bar"; }

Convention, not language grammar, decides which attributes matter. Two common ones the Pill names:

Say the done-criterion sentence: **config is a nixpkgs parameter. It is not part of Nix-the-language.** Confusing the two is how people invent myths about "Nix forbidding unfree." Nix evaluates expressions. nixpkgs refuses unfree when its config says so.

§V — .nix files as functions (one auto-call)

A .nix file holds an expression. That expression may be a derivation or a function returning a derivation. nix-build applies one rule:

  1. If the expression is a derivation, build it.
  2. If the expression is a function, call it once, then build the result.

Example that builds because pkgs has a default:

{ pkgs ? import <nixpkgs> {} }:

pkgs.psmisc

--arg / --argstr can still override pkgs. Nested function-returning-function is not auto-called twice. Only the first function encounter is invoked. That boundary matters when you accidentally wrap a package expression in a second lambda and wonder why nix-build refuses to proceed.

§VI — Laziness closes the set

import <nixpkgs> { … } returns the set of all packages. Laziness means only accessed derivations are built. Opening the set does not realize every attribute. That is why a full nixpkgs import is usable in a repl without compiling the world.

§VII — Worked import shapes

Minimal:

pkgs = import <nixpkgs> {};

Target Select plus Policy Gate together:

pkgs = import <nixpkgs> {
  system = "x86_64-linux";
  config = {
    allowUnfree = true;
    pulseaudio = true;
  };
};

Release-style pass-through:

{ system ? builtins.currentSystem }:
let
  pkgs = import <nixpkgs> {
    inherit system;
    config = { allowUnfree = false; };
  };
in
  pkgs.psmisc

§VIII — Common mistakes

  1. Believing ~/.config/nixpkgs/config.nix is baked into Nix. It is nixpkgs.
  2. Passing system only to nix-build -A while a nested import <nixpkgs> {} ignores it (forgot inherit system).
  3. Expecting nix-build to call a function twice when the file returns a function that returns a function.
  4. Jumping to flakes or packageOverrides before you can name system vs config.
  5. Treating pkgs.config as language syntax instead of an attribute on the imported set.

§IX — Boundaries

§X — Close

Session 10 customized one attribute. Session 11 opens the repository function: system picks the arch, config picks repository policy, and both sit in nixpkgs rather than in Nix-the-language. Next unmarked: session 12, Pills 17, nixpkgs overriding packages.

Related:

Open a repl. Run pkgs = import <nixpkgs> { config = { allowUnfree = true; }; }; pkgs.config and say which layer owns that attribute before you leave the keyboard.