Skip to content

Custom package and overlay flake

Overview

This walkthrough combines three nixpkgs patterns in one flake: a local package defined as a callPackage-shaped recipe, a set-level overlay that adds that package and patches an upstream attr (hello), and flake outputs so you can nix build the package and wire the same overlay into a NixOS host. Snippets wrap pkgs.hello—no tarball fetch or placeholder hashes that pretend to build.

For the concept layer see Overlay and callPackage. For packaging and overlay mechanics see Simple package and Writing overlays. For flake output conventions see Packages, apps, devShells.

Details

What you get

One repository with a recipe under pkgs/, an overlay file, a root flake.nix that exposes packages.<system>.default and overlays.default, and an optional NixOS module that applies the overlay from the flake input. After nix flake lock, nix build produces the custom wrapper; nixos-rebuild on a configured host sees both the new attr and the patched hello everywhere pkgs is used.

Overlay vs override: the overlay returns a fragment of pkgs merged into the fixed point—anything resolving pkgs.hello or pkgs.hello-wrapper sees your versions. A bare .override on one value outside an overlay only affects that binding. This example uses .overrideAttrs inside the overlay so the patched hello is set-wide; see Overlay vs override.

When you need fetch: recipes that download upstream sources use fixed-output fetchers (fetchurl, fetchFromGitHub, …). Those are fixed-output derivations; you obtain the hash from a failed build or nix-prefetch-url, not by guessing. The corpus fixture fod-fetchurl.nix shows the shape with an obviously invalid hash. This walkthrough avoids FOD entirely by wrapping hello from nixpkgs.

File layout

.
├── flake.nix
├── flake.lock                 # after nix flake lock
├── overlay.nix                # final: prev: { … }
├── pkgs/
│   └── hello-wrapper.nix      # callPackage recipe (see corpus simple-package.nix)
└── configuration.nix          # optional NixOS host module

Larger config repos split hosts/ and overlays/ the same way; see Config repo layout.

Annotated pieces

Local recipe (pkgs/hello-wrapper.nix), modeled on the corpus simple-package.nix. Arguments are filled by callPackage; the wrapper depends on nixpkgs' hello rather than fetching upstream:

{ lib, stdenv, hello }:

stdenv.mkDerivation {
  pname = "hello-wrapper";
  version = "0.1";

  dontUnpack = true;
  buildInputs = [ hello ];

  installPhase = ''
    mkdir -p $out/bin
    ln -s ${hello}/bin/hello $out/bin/hello-demo
  '';

  meta = with lib; {
    description = "Illustrative wrapper around hello for callPackage teaching";
    license = licenses.mit;
    platforms = platforms.all;
  };
}

The overlay (see overlay-snippet.nix) registers that recipe and patches upstream hello. Use prev for replacements and callPackage; reserve final when a new package must see attrs already merged into the fixed point:

final: prev: {
  hello-wrapper = prev.callPackage ./pkgs/hello-wrapper.nix { };

  hello = prev.hello.overrideAttrs (old: {
    pname = old.pname + "-patched";
  });
}

Root flake.nix pins nixpkgs, re-exports the overlay as overlays.default, and imports nixpkgs with that overlay when building packages.<system>.default. Downstream flakes can reuse overlays.default without copying the file; optional nixosConfigurations wires the same overlay into a host module:

{
  description = "Local callPackage recipe + overlay + flake packages";

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

  outputs = { self, nixpkgs, ... }@inputs:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
        overlays = [ self.overlays.default ];
      };
    in {
      overlays.default = import ./overlay.nix;

      packages.${system} = {
        default = pkgs.hello-wrapper;
        hello-wrapper = pkgs.hello-wrapper;
      };

      nixosConfigurations.demo = nixpkgs.lib.nixosSystem {
        specialArgs = { inherit inputs; };
        modules = [ ./configuration.nix ];
      };
    };
}

configuration.nix applies the overlay from flake inputs via specialArgs (same nixpkgs.overlays pattern as Writing overlays):

{ inputs, pkgs, ... }: {
  nixpkgs.overlays = [ inputs.self.overlays.default ];

  networking.hostName = "demo";

  environment.systemPackages = with pkgs; [
    hello-wrapper   # from overlay
    hello           # patched "-patched" pname via overlay
  ];

  system.stateVersion = "26.05";
}

nixpkgs.overlays applies to system nixpkgs evaluation only—it does not change standalone nix build unless you pass the same overlay list at import time.

Activate / verify

# nix.conf or --extra-experimental-features 'nix-command flakes'
nix flake lock
nix flake check
nix build                    # packages.<system>.default → ./result/bin/hello-demo
./result/bin/hello-demo

# optional NixOS (on a real or VM host with matching platform)
sudo nixos-rebuild build --flake .#demo

nix flake check evaluates packages and any nixosConfigurations.*.config.system.build.toplevel derivations.

Failure modes

Symptom Likely cause
attribute 'hello-wrapper' missing Built packages against plain legacyPackages without applying self.overlays.default
Patched hello not visible in a module Overlay not in nixpkgs.overlays, or a module imported pkgs via specialArgs bypassing module nixpkgs
experimental Nix feature 'flakes' is disabled Enable flakes and nix-command
Fetch hash errors after adding tarball src Expected for FOD—copy hash from Nix's error; see Hashing and inputs

Examples

Copy the File layout tree and the four blocks under Annotated pieces. Adapt system, the nixpkgs pin, and system.stateVersion for your host.

Downstream flake consuming only the overlay:

nixpkgs.overlays = [ inputs.myPkg.overlays.default ];

Operator sequence: see Activate / verify.

References

See also