Creating and Composing Overlays
Creating and Composing Overlays
You need a patch, not a nixpkgs fork. The boring default is: final: prev: { … }, prev.pkg.override / overrideAttrs, final for siblings, and a short overlay — not a private nixpkgs.
Mental model
final: prev: {
deskGreet = prev.writeShellScriptBin "desk-greet" "echo desk";
hello = prev.hello.overrideAttrs (old: { pname = old.pname; });
}| Arg | Meaning |
|---|---|
prev |
Package set before this overlay. Override this package from prev. |
final |
Package set after all overlays. Use for other packages this overlay also defines. |
final.hello.overrideAttrs when you meant prev.hello is infinite recursion.
Apply:
import nixpkgs { overlays = [ overlayA overlayB ]; }or NixOS:
# configuration.nix fragment
{
nixpkgs.overlays = [
(import ./overlays/desk.nix)
];
}List order is apply order. Later overlays see earlier ones through final. A third overlay that also sets hello wins if it uses prev from after the second — or recurses if it uses final.hello. Keep one overlay file per concern (overlays/hello.nix, overlays/desk-scripts.nix) and import the list.
Worked examples
Case 1: Add a script to pkgs
Save as overlay_demo.nix:
# overlay_demo.nix
let
deskOverlay = final: prev: {
deskGreet = prev.writeShellScriptBin "desk-greet" ''
echo "Welcome to the desk infrastructure"
'';
};
pkgs = import <nixpkgs> { overlays = [ deskOverlay ]; };
in
{
name = pkgs.deskGreet.name;
}nix eval --impure --file overlay_demo.nix --json(--impure because <nixpkgs>. In a flake, pass nixpkgs from inputs — no impure.)
Output:
{"name":"desk-greet"}
Case 2: Compose two overlays
Save as compose_overlays.nix:
# compose_overlays.nix
let
overlayA = final: prev: { servicePort = 8080; };
overlayB = final: prev: {
serviceUrl = "http://127.0.0.1:${toString final.servicePort}";
};
pkgs = import <nixpkgs> { overlays = [ overlayA overlayB ]; };
in
{ url = pkgs.serviceUrl; }nix eval --impure --file compose_overlays.nix --apply 'x: x.url'Output:
"http://127.0.0.1:8080"
overlayB reads final.servicePort so it sees overlay A’s binding.
Case 3: overrideAttrs from prev
# hello-overlay.nix
final: prev: {
hello = prev.hello.overrideAttrs (old: {
pname = "desk-hello";
});
}The output path name changes. Downstream pkgs.hello is your patched one if they take hello from the same pkgs.
pkg.override { enableFoo = true; } is for callPackage arguments. overrideAttrs is for the derivation attrset (postPatch, version). Using overrideAttrs to flip a callPackage flag does nothing. Read the package file: if it is { stdenv, enableFoo ? false }:, you want override.
Case 4: Infinite recursion (the foot-gun)
# bad-overlay.nix
final: prev: {
hello = final.hello.overrideAttrs (old: { });
}nix eval --impure --expr 'import <nixpkgs> { overlays = [ (import ./bad-overlay.nix) ]; }' -f '<nixpkgs>' hello --apply 'p: p.name'You will get infinite recursion (or a hang/error). Use prev.hello.
Case 5: Flake overlay export
# flake.nix fragment
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
outputs = { self, nixpkgs }: {
overlays.default = final: prev: {
deskGreet = prev.writeShellScriptBin "desk-greet" "echo desk";
};
};
}Consumers: overlays = [ desk-tooling.overlays.default ]; with follows on nixpkgs.
The trap
The trap is final.pkg.override…. Recursion.
The other trap is a 2000-line overlay that copies half of nixpkgs. Pin a package with fetchFromGitHub + callPackage (next chapter) or an overlay of one attr. Fork nixpkgs only when you are actually contributing upstream.
The boring rule
- Signature
final: prev:. - Override this package from
prev. - Refer to other overlay packages via
final. - Keep overlays small. Files under
overlays/. - 26.05
followsso the overlay applies to one pkgs.
Try this
- Overlay
desk-statuswithwriteShellScriptBin;nix-build -E 'with import <nixpkgs> { overlays = [ … ]; }; desk-status'(impure lab) or a flake package. - Two overlays: A sets
deskPort = 9;, B builds a string fromfinal.deskPort. - Write the bad
final.hellooverlay and trigger recursion; switch toprev. nix flake metadataafter adding an overlay-only flake input.