Creating and Building a Local Package within a Nixpkgs Clone
While an actual submission to Nixpkgs involves more steps, this chapter demonstrates the fundamental pattern for creating a package. Every package recipe is a file that declares a function. This function takes the packages dependencies as argument.
In this example we'll make a simple package with coreutils
and build it.
Demonstrating the process of building and testing a local package.
Clone Nixpkgs
First, we'll clone Nixpkgs and try to find a good spot to put our package. We're
just building a test package so nixpkgs/pkgs/misc
could be a good place to
start. We'll call our package testPackage
.
cd ~
mkdir src && cd src
git clone https://github.com/NixOS/nixpkgs.git
cd nixpkgs/pkgs
ls # Try to find a catagory that your pkg fits in
╭────┬────────────────┬──────┬─────────┬─────────────╮
│ # │ name │ type │ size │ modified │
├────┼────────────────┼──────┼─────────┼─────────────┤
│ 0 │ README.md │ file │ 50.6 kB │ 2 hours ago │
│ 1 │ applications │ dir │ 398 B │ 2 hours ago │
│ 2 │ build-support │ dir │ 2.5 kB │ 2 hours ago │
│ 3 │ by-name │ dir │ 2.9 kB │ 2 hours ago │
│ 4 │ common-updater │ dir │ 286 B │ 2 hours ago │
│ 5 │ data │ dir │ 82 B │ 2 hours ago │
│ 6 │ desktops │ dir │ 164 B │ 2 hours ago │
│ 7 │ development │ dir │ 882 B │ 2 hours ago │
│ 8 │ games │ dir │ 1.5 kB │ 2 hours ago │
│ 9 │ kde │ dir │ 116 B │ 2 hours ago │
│ 10 │ misc │ dir │ 390 B │ 2 hours ago │
│ 11 │ os-specific │ dir │ 42 B │ 2 hours ago │
│ 12 │ pkgs-lib │ dir │ 68 B │ 2 hours ago │
│ 13 │ servers │ dir │ 1.0 kB │ 2 hours ago │
│ 14 │ shells │ dir │ 46 B │ 2 hours ago │
│ 15 │ stdenv │ dir │ 178 B │ 2 hours ago │
│ 16 │ test │ dir │ 702 B │ 2 hours ago │
│ 17 │ tools │ dir │ 342 B │ 2 hours ago │
│ 18 │ top-level │ dir │ 2.3 kB │ 2 hours ago │
╰────┴────────────────┴──────┴─────────┴─────────────╯
Ad-hoc semi-regular structure, if you need to make a new package we first make a
directory with the name of the package and a default.nix
in said directory:
❗ NOTE: In this example we will use the
misc
directory, it is now recommended to use theby-name
directory. Explained further down.
Create your Package directory and a default.nix
cd misc
mkdir testPackage && cd testPackage
hx default.nix
# default.nix
{
runCommand,
coreutils,
}:
runCommand "testPackage" {
nativeBuildInputs = [
coreutils
];
} ''
echo 'This is a Test' > $out
''
Now we need to add our testPackage
to all-packages.nix
cd pkgs/top-level
hx all-packages.nix
all-packages.nix
is a centralized module that defines all available package
expressions.
We'll add our package in the list alphabetically:
# all-packages.nix
# `/msc` # editor search inside file
# Scroll down to t's
# snip ...
termusic = callPackage ../applications/autio/termusic { };
# we add our package here
testPackage = callPackage ../misc/testPackage { };
tfk8s = callPackage ../applications/misc/tfk8s { };
# snip ...
callPackage
is a core utility in Nixpkgs. It takes a Nix expression (like ourdefault.nix
file, which defines a function) and automatically provides the function with any arguments it declares, by looking them up within thepkgs
set (or the scope wherecallPackage
is invoked). This means you only need to list the dependencies your package needs in itsdefault.nix
function signature, andcallPackage
will "inject" the correct versions of those packages. This is what thecallPackage
Nix Pill demonstrates at a lower level.
Understanding pkgs/by-name/
and other locations
Nixpkgs uses different conventions for package placement:
-
Older categories (e.g.,
pkgs/misc/
,pkgs/applications/
): Packages within these directories typically usedefault.nix
as their definition file (e.g.,pkgs/misc/testPackage/default.nix
). These packages are NOT automatically included in the top-levelpkgs
set; they must be explicitly added via acallPackage
entry inpkgs/top-level/all-packages.nix
. This is the method demonstrated in this chapter for ourtestPackage
. -
The new
pkgs/by-name/
convention: This is the preferred location for new packages.-
Packages here are placed in a directory structure like
pkgs/by-name/<first-two-letters>/<package-name>/
. -
Crucially, their main definition file is named
package.nix
(e.g.,pkgs/by-name/te/testPackage/package.nix
). -
Packages placed within
pkgs/by-name/
are automatically discovered and exposed by Nixpkgs' top-levelpkgs
set. They do not require a manualcallPackage
entry inall-packages.nix
. This results in a more modular and scalable approach, reducing manual maintenance.
-
❗ : While this example uses
pkgs/misc/
to demonstrate explicitcallPackage
usage, when contributing a new package to Nixpkgs, you should nearly always place it withinpkgs/by-name/
and name its definition filepackage.nix
.
-
There are some Limitations to this approach.
Previously, packages were manually added to all-packages.nix
. While this is no
longer needed in most cases, understanding the old method provides useful
context for troubleshooting legacy configurations or custom integrations.
Try Building the Package
Move to the root directory of Nixpkgs:
cd ~/src/nixpkgs
Try building it:
nix-build -A testPackage
this derivation will be built:
this derivation will be built:
/nix/store/yrbjsxmgzkl24n75sqjfxbpv5cs3b9hc-testPackage.drv
building '/nix/store/yrbjsxmgzkl24n75sqjfxbpv5cs3b9hc-testPackage.drv'...
/nix/store/3012zlv30vn6ifihr1jxbg5z3ysw0hl3-testPackage
runCommand
is a simple builder, it takes 3 arguments. The first is the package
name the second is the derivation attributes, and the third is the script to
run.
cat ~/src/nixpkgs/result
───────┬──────────────────────────────
│ File: result
───────┼──────────────────────────────
1 │ This is a Test
───────┴──────────────────────────────
nix-instantiate --eval -A testPackage.meta.position
"/home/jr/src/nixpkgs/pkgs/misc/testPackage/default.nix:6"
Tools like nix search
and the Nixpkgs website use the meta
information for
documentation and discoverability. It can also be useful for debugging and helps
to provide better error messages. The above command shows that the
meta.position
attribute points to the file and line where the package
definition begins, which is very useful for debugging.
Typically a file will have a meta
attribute that looks similar to the
following:
meta = with lib; {
homepage = "https://www.openssl.org/";
description = "A cryptographic library that implements the SSL and TLS protocols";
license = licenses.openssl;
platforms = platforms.all;
} // extraMeta;
For example, the following shows how Nix is able to discover different parts of your configuration:
Launch the nix repl
and load your local flake:
cd /src
nix repl
nix-repl> :lf nixpkgs
nix-repl> outputs.legacyPackages.x86_64-linux.openssl.meta.position
"/nix/store/syvnmj3hhckkbncm94kfkbl76qsdqqj3-source/pkgs/development/libraries/openssl/default.nix:303"
nix-repl> builtins.unsafeGetAttrPos "description" outputs.legacyPackages.x86_64-linux.openssl.meta
{
column = 9;
file = "/nix/store/syvnmj3hhckkbncm94kfkbl76qsdqqj3-source/pkgs/development/libraries/openssl/default.nix";
line = 303;
}
Lets create just the meta.description
for demonstration purposes.
Adding the meta attribute
Since we don't have a meta
attribute this points to a default value that's
incorrect.
Let's add the meta
attribute and try it again:
# default.nix
{
runCommand,
coreutils,
}:
runCommand "testPackage" {
nativeBuildInputs = [
coreutils
];
meta = {
description = "test package";
};
} ''
echo 'This is a Test' > $out
''
nix-instantiate --eval -A testPackage.meta.position
"/home/jr/src/nixpkgs/pkgs/misc/testPackage/default.nix:11"
Now it points us to the 11'th line, right where our meta.description
is.
Let's stage our package so nix recognises it:
cd ~/nixpkgs
git add pkgs/misc/testPackage/
nix edit .#testPackage
The default.nix
that we've been working on should open in your $EDITOR