Day 31 — Overlays light
Day 31 — Overlays light
Stage III · ~4h
Goal: Apply a small overlay so the same package change is visible to the NixOS host and to devShells—without forking nixpkgs or cargo-culting overlay stacks.
Overlays are sharp tools. Prefer options, package overrides in a single module, or upstream pins when they suffice. Today teaches the mechanism so you are not helpless—not so every problem becomes an overlay.
Why this day exists
Sooner or later you need:
- A newer/older minor of one tool than nixpkgs 26.05 ships
- A one-line patch to a package you use
- A renamed or wrapped package for your lab
Overlays let you modify the pkgs fixed-point. Done lightly, they are elegant. Done heavily, they make upgrades impossible.
Theory 1 — What an overlay is
An overlay is a function:
final: prev: {
# new or overridden attrs
}| Argument | Meaning |
|---|---|
prev |
Package set before this overlay |
final |
Package set after all overlays (fixed-point)—use for deps on other overridden pkgs |
final: prev: {
hello = prev.hello.overrideAttrs (old: {
# example: add a patch or change configure flags
});
}Theory 2 — Wiring overlays on NixOS
# modules/common/nixpkgs.nix
{ ... }:
{
nixpkgs.overlays = [
(import ../../overlays/hello.nix)
# or inline:
# (final: prev: { ... })
];
}With home-manager.useGlobalPkgs = true, HM sees the same overlayed pkgs. That is why Day 25 set that flag.
Flake-level pkgs for shells/checks
pkgsFor = system: import nixpkgs {
inherit system;
overlays = [
(import ./overlays/hello.nix)
];
};Consistency rule: the overlay list used for nixosConfigurations should match the list used for devShells / checks when you care about the same tools—or document intentional differences.
Theory 3 — overrideAttrs vs override vs new package
| Tool | When |
|---|---|
pkg.override { ... } |
Change callPackage parameters |
pkg.overrideAttrs |
Change derivation attributes (patches, version, src) |
New attr final.mytool = ... |
Add a package name without clobbering |
Example patch (illustrative):
# overlays/hello.nix
final: prev: {
hello = prev.hello.overrideAttrs (old: {
patches = (old.patches or []) ++ [
# ./patches/hello-example.patch
];
});
}If you do not have a real patch today, use a harmless visible change such as pname suffix only for learning—or prefer adding a wrapper package instead of mutating hello:
final: prev: {
hello-lab = prev.writeShellScriptBin "hello-lab" ''
echo "lab overlay wrapper"
exec ${prev.hello}/bin/hello "$@"
'';
}Wrappers are often safer than overriding core packages.
Theory 4 — Composition order
nixpkgs.overlays = [ overlayA overlayB ];Later overlays see earlier results via prev chain. Conflicts: last writer wins for the same attr. Keep one file per concern and a short list.
Theory 5 — When not to overlay
| Need | Prefer |
|---|---|
| Enable a service option | NixOS module option |
| Package only for one project | Project devShell / packages |
| Temporary try | nix shell / branch pin |
| Huge fork of desktop stack | Separate nixpkgs input (advanced) |
Overlays that rewrite half of python3Packages are Stage V+ territory—and often a mistake.
Theory 6 — Pinning a package from another nixpkgs (light)
Sometimes you want one package from a newer pin without moving the whole OS:
# advanced sketch — use sparingly
# inputs.nixpkgs-unstable.url = "...";
# final: prev: {
# fancy = inputs.nixpkgs-unstable.legacyPackages.${prev.system}.fancy;
# }Costs: two package graphs, closure size, ABI surprises. For today, a wrapper or small overrideAttrs is enough.
Lab 1 — Create overlay file
mkdir -p overlays
# add hello-lab.nix and default.nix as aboveLab 2 — Attach to NixOS
Import overlay list via nixpkgs.overlays. Add pkgs.hello-lab to system or HM packages. Rebuild:
sudo nixos-rebuild switch --flake .#lab
hello-labLab 3 — Attach to devShell / checks pkgs
Ensure pkgsFor (or flake-parts pkgs) uses the same overlays.
nix develop -c hello-labIf only system has it, fix the flake pkgs construction.
Lab 4 — Prove HM sees it (if useGlobalPkgs)
# homes/alice snippet
home.packages = [ pkgs.hello-lab ];Rebuild; as user command -v hello-lab.
Lab 5 — Intentional override discipline
Journal:
- What you overlaid/wrapped and why
- How you will remove it when upstream catches up
- Whether a project-only shell would have been enough
Lab 6 — Negative space
Find one change you could do with an overlay but will do with a module option instead. Write it down—builds judgment.
Common gotchas
| Symptom / mistake | What to do |
|---|---|
pkgs.hello-lab missing |
Overlay not wired into that pkgs import |
| HM doesn’t see overlay | useGlobalPkgs false or separate pkgs |
| Infinite recursion in overlay | Using final incorrectly; simplify |
| Overlay only on system | Wire flake pkgsFor too |
| Overlaying all of nixpkgs for fun | Revert; minimize surface |
| Patch path wrong | Path relative to overlay file / flake root |
Checkpoint
- Overlay or wrapper package defined in repo
- Visible on NixOS host packages
- Visible in devShell or documented intentional difference
- HM consistency verified if applicable
- Removal plan written
- Judgment note: when not to overlay
Commit
git add .
git commit -m "day31: light overlays shared by host and shell"Write three personal gotchas before continuing.
Tomorrow
Day 32 — Stage III gate: prove host + Home Manager + devShell + checks live in one clean flake—exit criteria for daily-driver shape.