Overlays (light introduction)
Overlays (light introduction)
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. This chapter teaches the mechanism so you are not helpless—not so every problem becomes an overlay.
Why this chapter 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
});
}Fixed-point intuition
pkgs = fix (self: vanilla // overlay1 self vanilla // overlay2 self …)
You rarely write fix yourself; Nixpkgs applies overlays for you.
Theory 2 — Wiring overlays on NixOS
# modules/common/nixpkgs.nix
{ ... }:
{
nixpkgs.overlays = [
(import ../../overlays/hello-lab.nix)
# or inline:
# (final: prev: { ... })
];
}With home-manager.useGlobalPkgs = true, HM sees the same overlayed pkgs. That is why the Home Manager chapter set that flag.
Flake-level pkgs for shells/checks
pkgsFor = system: import nixpkgs {
inherit system;
overlays = [
(import ./overlays/hello-lab.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, use a harmless visible change—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.
# overlays/default.nix
[
(import ./hello-lab.nix)
# (import ./mypackage.nix)
]nixpkgs.overlays = import ../../overlays/default.nix;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) |
| One CLI flag | Wrapper script in module |
Overlays that rewrite half of python3Packages are packaging-part territory—and often a mistake for hosts.
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 = "github:NixOS/nixpkgs/nixos-unstable";
# final: prev: {
# fancy = inputs.nixpkgs-unstable.legacyPackages.${prev.system}.fancy;
# }Costs: two package graphs, closure size, ABI surprises. In this chapter, a wrapper or small overrideAttrs is enough.
Theory 7 — Overlays vs packageOverrides (classic)
Older NixOS configs used nixpkgs.config.packageOverrides. Prefer nixpkgs.overlays in modern flakes. If you find both, consolidate.
Theory 8 — Infinite recursion patterns
| Anti-pattern | Why |
|---|---|
final.foo = final.foo.override… carelessly |
Wrong fixed-point use |
| Overlay imports a module that imports pkgs that need the overlay | Cycles |
Using final where prev suffices |
Accidental recursion |
Rule of thumb: take base package from prev; reach for final only when depending on another new attr you defined in the same overlay set.
final: prev: {
myLib = prev.callPackage ./mylib { };
myApp = prev.callPackage ./myapp { inherit (final) myLib; };
}Worked example — HM sees the overlay
# homes/alice/programs.nix fragment
{ pkgs, ... }:
{
home.packages = [ pkgs.hello-lab ];
}Requires home-manager.useGlobalPkgs = true.
Exercises
Exercise 1 — Create overlay file
mkdir -p overlays
# add hello-lab.nix and default.nix as aboveExercise 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-labExercise 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.
Exercise 4 — Prove HM sees it (if useGlobalPkgs)
home.packages = [ pkgs.hello-lab ];Rebuild; as user command -v hello-lab.
Exercise 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
Exercise 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.
Exercise 7 — Composition list
Add a second tiny wrapper (cowsay-lab or similar). Confirm both appear. Practice ordering in overlays/default.nix.
Exercise 8 — Failure: overlay not wired
Temporarily remove overlay from pkgsFor only; observe shell missing package while system has it. Restore consistency.
Exercise 9 — Document overlays
Add docs/overlays.md: list each overlay, purpose, removal criteria.
Exercise 10 — Check integration
If you have flake checks, add a check that builds pkgs.hello-lab or run it in the shell check path.
Exercise 11 — Prefer wrapper over override
If you mutated hello, convert to hello-lab wrapper instead. Rebuild.
Exercise 12 — Search before overlay
For one real need, search nixpkgs options / HM options first. Only overlay if no option exists.
Exercise 13 — Recursion caution note
Write two sentences in docs/overlays.md about final vs prev.
Exercise 14 — Upgrade readiness
When nixpkgs updates, re-run hello-lab and note if still needed.
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 |
| Two competing overlay lists | One source of truth |
| Clobbering upstream attr accidentally | Prefer new attr names |
| Project-only need in host overlay | Move to project flake |
| Forgot removal plan | Document in docs/overlays.md |
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
- Single overlay composition list
- Docs list active overlays
Journal (optional)
Write three personal gotchas before continuing.
Part close-out
You now have:
| Capability | Chapter |
|---|---|
| Scalable repo layout | Flake repository layout |
| Deliberate pins | Lock file discipline |
| User plane via HM module | Home Manager + files + services |
| Project toolchains | direnv + devShells |
| Multi-output style | flake-parts or hand-rolled |
| Quality gates | Flake checks |
| Light package tweaks | Overlays |
Next part: Services and security patterns—secrets, hardening, firewall, TLS, data services, containers, observability—on top of this shape.