nix-packaging
Skill2 starsChanged 4 months agoMITView on GitHub
Use when writing Nix packages from scratch, authoring derivations with stdenv.mkDerivation or language-specific builders (buildPythonPackage, buildRustPackage, buildGoModule), debugging build phase failures, patching package sources, or managing build/runtime dependencies. Also trigger on closure size analysis, wrapProgram, substituteInPlace, or fetchpatch. NOT for system configuration, dev shells, or customizing existing packages with override/overrideAttrs (see nix).
1 other project uses a version of this.
Adopted by
Most recently taken up by
- Shangshui0302/some-NixOS-skillstheir own version, first seen Oct 7, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- Shangshui0302/some-NixOS-skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository67 other files it gives its agents.
- AGENTS.mdAGENTS.md
- agent-architectureSkill
- agent-evaluationSkill
- agent-memorySkill
- agent-post-trainingSkill
- agent-self-evolutionSkill
- agent-tool-designSkill
- api-designSkill
- application-architectureSkill
- brainstormSkill
- code-reviewerSkill
- coding-agent-designSkill
- concurrency-designSkill
- context-engineeringSkill
- dockerSkill
- game-audioSkill
- game-designSkill
- game-narrativeSkill
- game-patternsSkill
- game-performanceSkill
- game-renderingSkill
- game-visualsSkill
- git-commitSkill
- git-prSkill
- godot-shaderSkill
- godotSkill
- gpu-rendering-architectureSkill
- grafanaSkill
- helmSkill
- homelab-monitoringSkill
- k3sSkill
- k8s-networkingSkill
- k8s-operationsSkill
- k8s-storageSkill
- k8s-workloadsSkill
- knowledge-baseSkill
- level-designSkill
- llm-inference-servingSkill
- loggingSkill
- logqlSkill
- love2d-fennelSkill
- love2dSkill
- ml-platform-architectureSkill
- ml-post-trainingSkill
- multi-agent-collaborationSkill
- nix-dendriticSkill
- nixSkill
- odin-designSkill
- odin-gamedevSkill
- orchestratorSkill
- physical-ai-systemsSkill
- plannerSkill
- prd-to-storiesSkill
- prd-writingSkill
- promqlSkill
- pr-reviewerSkill
- python-designSkill
- rag-designSkill
- ray-data-pipelinesSkill
- ray-kubernetes-infrastructureSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 1 is distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 17
Every package installs to its own unique store path ($out).
- Very specific: line 87
Prefer hooks over overrides: Use preConfigure, postBuild, postInstall, etc.
- Distinctive: line 205
Always document patches with: what they fix, a link to the upstream PR/issue, and when they can be removed.
- Very specific: line 252
Use dependencies for Python runtime deps (not propagatedBuildInputs).
- Very specific: line 316
If the module has no external dependencies, use vendorHash = null;.
- Very specific: line 355
When another package lists yours in buildInputs, it gets the .dev output by default (if it exists), which contains headers.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Nix Packaging Guide
1. Packaging Mental Model
A Nix package goes through two distinct stages:
- Instantiation: The Nix expression evaluates to a
.drvfile in the store. This is pure evaluation — no I/O, no network, no side effects. - Realisation: The
.drvis built, producing a store path like/nix/store/abc123-mypkg-1.0/. This is where compilation happens.
Every package installs to its own unique store path ($out). Nothing is shared via FHS paths like /usr/lib — every dependency is referenced by its full store path.
Why everything must be explicit:
- The sandbox blocks all network access during realisation (except for Fixed-Output Derivations)
- There is no implicit
PATH— tools must be innativeBuildInputsor explicitly referenced - No
/usr/bin,/lib, or other FHS paths exist — hardcoded paths in source must be patched
Derivation types:
- Fixed-Output Derivations (FODs): Fetchers like
fetchFromGitHub. They have a known output hash, so Nix permits network access. These are the leaves of the build closure. - Input-Addressed Derivations: Normal packages. The output hash is derived from all inputs — same inputs always produce the same output.
- Content-Addressed Derivations: Experimental. The hash is of the actual output content, enabling deduplication across rebuilds.
2. stdenv.mkDerivation Scaffold
{ lib, stdenv, fetchFromGitHub, cmake, pkg-config, openssl, zlib }:
stdenv.mkDerivation rec {
pname = "mypkg";
version = "1.2.3";
src = fetchFromGitHub {
owner = "example";
repo = "mypkg";
rev = "v${version}";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
# Tools that run on the BUILD machine (compilers, code generators, pkg-config)
nativeBuildInputs = [ cmake pkg-config ];
# Libraries linked into the final binary (for the HOST machine)
buildInputs = [ openssl zlib ];
# Flags passed to cmake/configure
cmakeFlags = [
"-DENABLE_TESTS=OFF"
];
# Override a phase completely — ALWAYS include runHook calls or pre/postPhase hooks break
buildPhase = ''
runHook preBuild
make -j$NIX_BUILD_CORES
runHook postBuild
'';
# Extend a phase without replacing it (preferred over overriding)
postInstall = ''
install -Dm644 LICENSE $out/share/licenses/mypkg/LICENSE
'';
# Skip phases you don't need
# dontConfigure = true;
# dontBuild = true;
# dontFixup = true;
meta = with lib; {
description = "A short description";
homepage = "https://github.com/example/mypkg";
license = licenses.mit;
maintainers = [ ];
platforms = platforms.unix;
};
}Phase execution order: unpackPhase → patchPhase → configurePhase → buildPhase → checkPhase → installPhase → fixupPhase
The runHook rule: Whenever you override a phase entirely (e.g., buildPhase = ''...''), you MUST include runHook preBuild at the start and runHook postBuild at the end. Without these, any preBuild/postBuild hooks set elsewhere (including by setup hooks from dependencies) will silently not run. This is the most common cause of mysterious build failures after adding a dependency.
Prefer hooks over overrides: Use preConfigure, postBuild, postInstall, etc. to extend phases rather than replacing them. This composes better with setup hooks from dependencies.
See the nixpkgs manual phases reference for the full phase API.
3. Dependency Taxonomy
Getting this right matters for cross-compilation. Even for native builds, correct placement is good practice.
| Attribute | What goes here | Rule of thumb |
nativeBuildInputs | Tools that run during the build: cmake, pkg-config, makeWrapper, protobuf, code generators, python3 (if used to generate code) | "Does this tool execute on my machine during compilation?" |
buildInputs | Libraries linked into the final binary: openssl, zlib, gtk3, boost | "Does this get linked into the output binary?" |
propagatedBuildInputs | Libraries whose headers/modules consumers also need | "Will packages that depend on mine also need to find these headers?" |
propagatedNativeBuildInputs | Build tools that consumers also need | Rare — mostly for language ecosystems |
The cross-compilation reason: On a native build, both lists end up on PATH and PKG_CONFIG_PATH similarly. But in cross-compilation, nativeBuildInputs runs on the build machine (x86_64) while buildInputs targets the host machine (aarch64). Mixing them breaks cross builds.
propagatedBuildInputs warning: Every entry increases the closure size of every package that depends on yours. Use it only when consumers genuinely need the headers or modules at build time (common for C libraries with public headers, Python libraries, pkg-config .pc files that reference other .pc files).
Common mistakes:
cmakeinbuildInputsinstead ofnativeBuildInputs— cmake runs during build, not at runtimepkg-configinbuildInputs— same issue- A Python library in
nativeBuildInputswhen it's a runtime import — it won't be in the closure
4. Runtime Dependencies & wrapProgram
Nix determines runtime dependencies automatically: after the build, it scans every file in $out for store path hashes. If /nix/store/abc123-openssl-3.0 appears anywhere in your output (binary, script, config file), openssl becomes a runtime dependency.
This means:
- You don't declare runtime deps explicitly — they're detected from the output
- A script that
execs another program won't pull that program into the closure unless the path is hardcoded - Scripts that shell out to tools like
curlorjqneed those tools injected viawrapProgram
wrapProgram pattern for scripts that shell out:
{ lib, stdenv, makeWrapper, curl, jq }:
stdenv.mkDerivation {
# ...
nativeBuildInputs = [ makeWrapper ]; # makeWrapper is a build tool
postInstall = ''
wrapProgram $out/bin/myscript \
--prefix PATH : ${lib.makeBinPath [ curl jq ]}
'';
}wrapProgram replaces the binary with a wrapper script that sets environment variables before exec-ing the original. --prefix PATH : prepends to PATH rather than replacing it.
wrapProgram creates wrapper scripts that embed store paths. When diagnosing an unexpectedly large closure, check whether your binary is actually a wrapper script:
file /nix/store/xxx-mypkg/bin/mybinary
# "POSIX shell script" means wrapProgram was used — the script embeds store paths
# for every tool in --prefix PATH, pulling them into the runtime closure
strings /nix/store/xxx-mypkg/bin/mybinary | grep /nix/store/This is the most common source of unexpected closure entries in packages that use wrapProgram.
Closure analysis commands:
nix-store -q --references /nix/store/xxx-mypkg # immediate deps
nix-store -q --requisites /nix/store/xxx-mypkg # full transitive closure
nix why-depends /nix/store/xxx-mypkg /nix/store/yyy-dep # trace why dep is included
nix path-info -Sh /nix/store/xxx-mypkg # closure size (human-readable)5. Patching
substituteInPlace for fixing hardcoded paths:
# Inside a postPatch = ''...'' Nix string — ${curl} is Nix interpolation, $out is a shell variable:
substituteInPlace src/config.c \
--replace-fail '/usr/bin/curl' '${curl}/bin/curl'
# Multiple replacements:
substituteInPlace Makefile \
--replace-fail '/usr/local' "$out" \
--replace-fail 'gcc' "$CC"Always use --replace-fail (not --replace). The --replace flag emits a deprecation warning but does not fail the build when the pattern is absent — you'll get a binary that still has the hardcoded path and crashes at runtime. --replace-fail errors if the pattern is absent, catching the problem at build time.
Quoting note: Inside Nix ''...'' strings (multiline phase attributes), ${curl} is Nix interpolation (expands to the store path at instantiation time) and $out, $CC, $NIX_BUILD_CORES are shell variables (available at build time). Never confuse the two: ${CC} in a ''...'' string would try to interpolate a Nix variable named CC, not the shell variable.
replaceVars for template substitution with @variable@ patterns — a Nix-level function that returns a store path:
# replaceVars is a Nix function, not a shell command — use it as an attribute value:
someScript = replaceVars ./myscript.sh.in {
inherit curl;
bash = "${bash}/bin/bash";
};
# substituteAll is a shell function for use inside phase strings:
postPatch = ''
substituteAll ${./config.h.in} config.h
'';replaceVars is type-safe — it fails if any @variable@ in the template doesn't have a corresponding Nix variable. substituteAll substitutes all in-scope Nix variables; replaceVars takes an explicit attrset and is preferred when you want to be explicit.
fetchpatch for upstream patches:
patches = [
./fix-local.patch # local patch file
(fetchpatch {
# Upstream PR #1234 fixing the crash on arm64
# Remove when upgrading past v2.1.0
url = "https://github.com/example/pkg/commit/abc123.patch";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
})
];Always document patches with: what they fix, a link to the upstream PR/issue, and when they can be removed.
Creating a local patch:
# Non-flake:
nix-shell -A mypkg # enter build env
# Flake:
nix develop .#mypkg # enter dev shell with build tools
unpackPhase # extract source
cd $sourceRoot
git init && git add -A && git commit -m "pristine"
# make your changes
git diff > $OLDPWD/fix.patch6. Language Builders
Python (buildPythonPackage / buildPythonApplication)
{ lib, buildPythonApplication, fetchFromGitHub,
setuptools, requests, click }:
buildPythonApplication {
pname = "mytool";
version = "1.0.0";
src = fetchFromGitHub {
owner = "example";
repo = "mytool";
rev = "v1.0.0";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
build-system = [ setuptools ]; # PEP 517 build backend
dependencies = [ requests click ]; # runtime Python deps
# Avoid doCheck = false unless tests are structurally incompatible with the
# sandbox (e.g., require a running database). Disabling tests removes the
# only build-time behavioral verification for third-party code.
# doCheck = false;
}Key gotchas:
buildPythonApplicationvsbuildPythonPackage: UsebuildPythonApplicationfor executables (installs to$out/bin, not importable as a library). UsebuildPythonPackagefor libraries (importable by other packages).- Use
dependenciesfor Python runtime deps (notpropagatedBuildInputs). The Python infrastructure handles propagation automatically. build-systemis for the PEP 517 build backend (setuptools, flit-core, hatchling, etc.) — not for runtime deps.- To use a specific Python version:
python311.pkgs.buildPythonApplication { ... }orpkgs.python311Packages.callPackage ./pkg.nix { }.
See the nixpkgs Python packaging guide for the full API including test frameworks and wheel support.
Rust (buildRustPackage)
{ lib, rustPlatform, fetchFromGitHub, openssl, pkg-config }:
rustPlatform.buildRustPackage {
pname = "mytool";
version = "1.0.0";
src = fetchFromGitHub {
owner = "example";
repo = "mytool";
rev = "v1.0.0";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
# Option 1: lock file (preferred — reproducible)
cargoLock.lockFile = ./Cargo.lock;
# Option 2: vendor hash (set to lib.fakeHash first, use error output)
# cargoHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
nativeBuildInputs = [ pkg-config ];
buildInputs = [ openssl ];
}Key gotchas:
cargoHashmust be updated wheneverCargo.lockchanges. Set it tolib.fakeHashfirst, build to get the error with the correct hash, then update.cargoLock.lockFileis more reproducible — it vendors dependencies from the lock file directly.- For complex builds (workspaces, custom build scripts, C FFI), consider crane or naersk as alternatives.
Go (buildGoModule)
{ lib, buildGoModule, fetchFromGitHub }:
buildGoModule {
pname = "mytool";
version = "1.0.0";
src = fetchFromGitHub {
owner = "example";
repo = "mytool";
rev = "v1.0.0";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
# Build only specific packages (omit to build all)
subPackages = [ "cmd/mytool" ];
}Key gotchas:
- Set
vendorHash = lib.fakeHash;first — this is a bootstrap sentinel that intentionally fails the build with a hash mismatch error that prints the correct hash. Replace it with that hash before committing. Never commitvendorHash = lib.fakeHashorvendorHash = ""as final values — they will always fail the build. (vendorHash = nullis valid only for modules with no external dependencies.) subPackageslimits whichcmd/packages are built — useful for repos with multiple binaries where you only want one.- If the module has no external dependencies, use
vendorHash = null;.
C/C++ with CMake or Meson
{ lib, stdenv, fetchFromGitHub, cmake, pkg-config, glib }:
stdenv.mkDerivation {
# ...
nativeBuildInputs = [ cmake pkg-config ]; # cmake/meson are build tools
buildInputs = [ glib ];
cmakeFlags = [
"-DBUILD_TESTS=OFF"
"-DCMAKE_BUILD_TYPE=Release"
];
# For meson, use: nativeBuildInputs = [ meson ninja pkg-config ];
# and mesonFlags = [ "-Dtests=disabled" ];
}cmake and meson go in nativeBuildInputs — they're build tools, not linked libraries. See the nixpkgs cmake setup hook docs for how cmake integration works automatically.
7. Multiple Outputs
Split a package into separate outputs to reduce closure size for consumers:
stdenv.mkDerivation {
# ...
outputs = [ "out" "dev" "lib" "man" ];
# out: binaries and data files
# dev: headers and pkg-config files
# lib: shared libraries
# man: man pages
}When another package lists yours in buildInputs, it gets the .dev output by default (if it exists), which contains headers. The runtime closure only needs .lib or .out.
# Explicitly select an output:
buildInputs = [ mylib.dev ]; # headers
buildInputs = [ mylib.lib ]; # shared library onlyHelper functions: lib.getDev pkg, lib.getLib pkg, lib.getBin pkg.
When to use multiple outputs: C/C++ libraries where headers and shared libs are separate concerns. A package with 50MB of headers and 2MB of runtime library — consumers only need the 2MB at runtime. Reduces runtime closure by 30-50% in typical cases.
8. Common Mistakes
List/function application ambiguity:
# WRONG: two list elements — the function and the attrset
buildInputs = [ somePackage.override { enableFeature = true; } ];
# CORRECT: parentheses make it one element
buildInputs = [ (somePackage.override { enableFeature = true; }) ];Nix's function application is whitespace-sensitive. f { } applies f to { }, but [ f { } ] is a list with two elements.
Excessive with:
# AVOID: introduces 15,000+ names into scope, makes code hard to read
with pkgs; [ cmake openssl zlib ]
# PREFER: scope `with` tightly to the list
nativeBuildInputs = with pkgs; [ cmake pkg-config ];
buildInputs = with pkgs; [ openssl zlib ];URL-like strings:
# WRONG: parsed as a URL string, not a function
f = x:x;
# CORRECT: space after colon required
f = x: x;Forgetting runHook in phase overrides:
# WRONG: breaks preBuild/postBuild hooks from dependencies
buildPhase = ''
make -j$NIX_BUILD_CORES
'';
# CORRECT
buildPhase = ''
runHook preBuild
make -j$NIX_BUILD_CORES
runHook postBuild
'';Hardcoding the compiler:
# WRONG: breaks cross-compilation and clang stdenvs
buildPhase = "gcc -o mybin src.c";
# CORRECT: stdenv sets $CC to the right compiler
buildPhase = "$CC -o mybin src.c";Silent substituteInPlace failures:
# WRONG: emits a warning but does not fail the build when pattern is absent
substituteInPlace src/config.c --replace '/usr/bin/curl' '${curl}/bin/curl'
# CORRECT: fails the build if pattern is absent
substituteInPlace src/config.c --replace-fail '/usr/bin/curl' '${curl}/bin/curl'9. Debugging Toolkit
Interactive build environment (legacy/non-flake):
nix-shell -A mypkg # enter the build sandbox environment (non-flake only)
unpackPhase # extract source to $sourceRoot
cd $sourceRoot
configurePhase # run configure/cmake
buildPhase # attempt the buildInteractive build environment (flakes):
# Enter the package's build environment (not a devShell).
# nix develop resolves .#mypkg to packages.<system>.mypkg automatically:
nix develop .#mypkg
unpackPhase && cd $sourceRoot
configurePhase
buildPhaseThis lets you run phases manually, inspect errors, and test fixes without a full rebuild cycle.
Build logs:
nix build .#mypkg -L # verbose build output
nix build .#mypkg --print-build-logs # same, explicit flag
nix log /nix/store/xxx-mypkg.drv # read logs after failureClosure analysis:
nix why-depends /nix/store/xxx-mypkg /nix/store/yyy-dep # trace why dep is included
nix path-info -Sh /nix/store/xxx-mypkg # closure size
nix-tree /nix/store/xxx-mypkg # TUI closure browser (install nix-tree)Evaluating expressions:
nix repl
nix-repl> :lf . # load current flake
nix-repl> pkgs.mypkg.drvPath # inspect the .drv path
nix-repl> pkgs.mypkg.buildInputs # inspect resolved depsReferences
| Reference | When to read it |
| nixpkgs manual — stdenv phases | Full phase lifecycle, all hooks, genericBuild internals |
| nixpkgs manual — Python | buildPythonPackage full API, test frameworks, wheel support |
| nixpkgs manual — Rust | buildRustPackage full API, cross-compilation, workspace builds |
| nixpkgs manual — Go | buildGoModule full API, CGO, vendoring |
| nixpkgs manual — cmake setup hook | How cmake integration works automatically |
| nix-book | Deep packaging reference, derivation internals |
../nix/references/nixpkgs-advanced.md | callPackage, overlays, overrides, fetchers, trivial builders |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

