This document explains how to take advantage of software provided by Nix for people new to the Nix package manager. This guide uses this project for examples but focuses on introducing general Nix usage, which applies to other projects using Nix.
This project supports a still-experimental feature of Nix called flakes, which this document shows users how to use without. Another document explains how to do everything illustrated in this document, but with flakes.
NOTE: If you're new to flakes, please read the provided supplemental introduction to Nix to understand the experimental nature of flakes and how it may or may not affect you. Hopefully, you'll find these trade-offs acceptable to take advantage of the improved experience flakes offer.
Although this document avoids enabling the experimental flakes feature, it encourages some usage of the “nix-command” experimental feature. This feature exposes a variety of subcommands on the nix
command-line tool. These subcommands have been widely used and are considered safe. However, as still marked as experimental, their input parameters or output formats are subject to change. Be aware when scripting against them.
This document only uses these experimental nix
subcommands when there exists no other alternatives, or when the alternatives are considered worse for new users.
This project uses Nix to download all necessary dependencies and build everything from source. In this regard, Nix is helpful as not just a package manager but also a build tool. Nix helps us get from raw source files to built executables in a package we can install with Nix.
Within this project, the various files with a .nix
extension are Nix files, each of which contains an expression written in the Nix expression language used by the Nix package manager to specify packages. If you get proficient with this language, you can use these expressions as a starting point to compose your own packages beyond what's provided in this project.
If you're new to Nix consider reading the provided introduction.
This project supports
- Linux on x86-64 machines
- MacOS on x86-64 machines
- MacOS on ARM64 machines (M1 or M2).
That may affect your ability to follow along with examples.
Otherwise, see the provided Nix installation and configuration guide if you have not yet set Nix up.
To continue following this usage guide, you won't need Nix's experimental flakes feature enabled.
Though comprehensively covering Nix is beyond the scope of this document, we'll go over a few commands illustrating some usage of Nix with this project.
As the introduction mentions, Nix builds are specified by Nix expressions written in the Nix programming language and saved in files with a .nix
extension. These Nix files can be collocated with the source they build and package, but this isn't necessary or always the case. Some Nix files retrieve all the source or dependencies they need from the internet.
Various Nix commands accept file paths to Nix files as arguments. If a file path is a directory, a file named default.nix
is referenced within.
This project has a default.nix
file at its root. This file contains a Nix expression allowing users to access this project's flake outputs (defined in flake.nix
) without enabling the experimental flakes feature. Using the default.nix
file instead of using the flake directly comes at the cost of some extra time evaluating the expression.
The Nix expressions of projects often evaluate to attribute trees of packages. We can select out these packages by traversing an attribute path. These attribute paths are dot-delimited.
The non-experimental way of exploring what's in a Nix expression is to load it into a REPL session and to tab-complete through the various attributes of the tree:
nix repl --file .
Using the --file
switch tells nix repl
to load attributes from a Nix file. If the Nix expression in this file evaluates to an attribute set (a map of attribute names to values), the attributes of this set are bound to variables within the REPL session. Nested attribute sets build up our attribute tree.
Though experimental, the command nix search
is safe and helpful. Just be aware that input parameters/switches or output formatting might change with later releases if you script using it.
We can use an --extra-experimental-features nix-command
switch to use an experimental feature with nix
for a single call. Putting this all together, this is how we'd search the provided default.nix
file:
nix --extra-experimental-features nix-command search --file . '' ^
* packages.aarch64-darwin.nix-scaffold
Script to scaffold a Nix project
* packages.aarch64-darwin.org2gfm
Script to export Org-mode files to GitHub Flavored Markdown (GFM)
* packages.x86_64-darwin.nix-scaffold
Script to scaffold a Nix project
…
If you have disabled the nix-command
feature, and typing out nix --extra-experimental-features nix-command
is too verbose for your tastes, consider setting an alias in your shell such as the following:
alias nix-new = nix --extra-experimental-features 'nix-command'
Passing in --file .
tells nix search
to get the attribute tree to search from the default.nix
file in the current directory.
The first positional argument is the attribute path to start the search from within this tree. An empty string indicates to start at the root of the tree.
Remaining arguments are regexes to filter our search results with. Above we've passed ^
to match everything and return all results.
Note, there are some projects for which nix search
won't work. These projects require extra approaches to work with nix search
that are beyond the scope of this document. You can still navigate these projects' attribute tree with nix repl
. Or you can try to read the source code of the Nix expressions.
nix --extra-experimental-features nix-command \
search --file . '' linux org2gfm
* packages.x86_64-linux.org2gfm
Script to export Org-mode files to GitHub Flavored Markdown (GFM)
We can also use --json
to get more details about found packages:
nix --extra-experimental-features \
nix-command search --json --file . '' ^ | jq .
{
"packages.aarch64-darwin.nix-scaffold": {
"description": "Script to scaffold a Nix project",
"pname": "nix-scaffold",
"version": ""
},
"packages.aarch64-darwin.org2gfm": {
"description": "Script to export Org-mode files to GitHub Flavored Markdown (GFM)",
"pname": "org2gfm",
…
Additional data includes the package's name (from the “pname” field), a version string, and a textual description of the package.
You may find yourself downloading the source code of projects to inspect Nix files they provide.
Unfortunately, without enabling flakes, you can't use nix search
with Nixpkgs, the main repository providing packages for the Nix ecosystem. Without flakes, Nixpkgs has too many packages for Nix to evaluate. You can, though, tab-complete through Nixpkgs with nix repl --file $LOCAL_NIXPKGS_CHECKOUT
. As an alternative, consider using NixOS's online search engine.
If you decide to eventually try out flakes, you'll find it allows you to comfortably search all projects providing a flake.nix
file, including Nixpkgs, without even having to clone Git repositories yourself.
This document intentionally doesn't cover the nix-channel
command or the NIX_PATH
environment variable. Using either of these legacy features of Nix leads systems to unnecessary unreliability, compromising the reasons to advocate for Nix in the first place. Flakes have a registry system that addresses this problem. If you want to track and access remote repositories without flakes, access them with an explicit checkout of a pinned version/commit.
The following result is one returned by an execution of nix search
or tab-completing from within a nix repl
session:
{
"packages.x86_64-linux.org2gfm": {
"description": "Script to export Org-mode files to GitHub Flavored Markdown (GFM)",
"pname": "org2gfm",
"version": ""
}
}
We can see that a package can be accessed with the packages.x86_64-linux.org2gfm
output attribute path of the project's flake. Not shown in the search results above, this package happens to provide the executable bin/org2gfm
.
We can build this package with nix-build
from the project root:
nix-build --attr packages.x86_64-linux.org2gfm .
/nix/store/islqrpxqp250lq2f8mda3jfngbcm280l-org2gfm
If we omit the path to a Nix file, nix-build
will try to build default.nix
in the current directory. If we omit the --attr
switch and argument, nix-build
will try to build packages it finds in the root of the attribute tree.
All packages built by Nix are stored in /nix/store
. Nix won't rebuild packages found there. Once a package is built, its content in /nix/store
is read-only (until the package is garbage collected, discussed later).
The output of nix-build
shows us where in /nix/store
our package has been built. Additionally, as a convenience, nix-build
creates one or more symlinks for each package requested in the current working directory. These symlinks by default have a name prefixed with “result” and point back to the respective build in /nix/store
:
readlink result*
/nix/store/islqrpxqp250lq2f8mda3jfngbcm280l-org2gfm
Following these symlinks, we can see the files the project provides:
tree -l result*
result
└── bin
└── org2gfm
2 directories, 1 file
It's common to configure these “result” symlinks as ignored in source control tools (for instance, for Git within a .gitignore
file).
nix-build
has a --no-out-link
switch in case you want to build packages without creating “result” symlinks.
We can run commands in Nix-curated environments with nix shell
, provided we're okay enabling the nix-command
experimental feature. Nix will take executables found in packages, put them in an environment's PATH
, and then execute a user-specified command.
With nix shell
, you don't even have to build the package first with nix build
or mess around with “result” symlinks. nix shell
will build any necessary packages required.
For example, to get the help message for the org2gfm
executable provided by the package selected by the packages.x86_64-linux.org2gfm
attribute path output by this project's flake, we can call the following:
nix --extra-experimental-features 'nix-command' \
shell \
--file . \
packages.x86_64-linux.org2gfm \
--command org2gfm --help
USAGE: org2gfm [OPTION]... [FILE]...
DESCRIPTION:
Uses Emacs to export Org-mode files to GitHub Flavored
…
Like other Nix commands, using --file .
tells nix shell
to read a Nix expression from ./default.nix
. The positional arguments when calling nix shell
with --file
are the attribute paths selecting packages to put on the PATH
.
Note, if you don't use the --file
switch, nix shell
will assume you are working with a flake.
The command to run within the shell is specified after the --command
switch. nix shell
runs the command in a shell set up with a PATH
environment variable, including all the bin
directories provided by the selected packages.
If you want to enter a shell with the set up PATH
, you can drop the --command
switch and following arguments.
nix shell
also supports an --ignore-environment
flag that restricts PATH
to only packages selected, rather than extending the PATH
of the caller's environment. With --ignore-environment
, the invocation is more sandboxed.
The nix run
command allows us to run executables from packages with a more concise syntax than nix shell
with a --command
switch. Like nix search
, and nix shell
, this requires enablement of the experimental nix-command
feature.
Different from what nix shell
does, nix run
detects which executable to run from a package. nix run
assumes the specified package provides an executable with the same name as the package.
Remember, the package's name is not the same as the attribute used to select a package. The name is package metadata not shown by the default output of nix search
, but we can get to it by using --json
:
nix --extra-experimental-features \
nix-command search --file . --json 'packages.x86_64-linux.org2gfm' ^ | jq .
{
"": {
"description": "Script to export Org-mode files to GitHub Flavored Markdown (GFM)",
"pname": "org2gfm",
"version": ""
}
}
The “pname” field in the JSON above indicates the package's name. In practice, this name may differ from the last attribute in the attribute path. As detailed in man nix3-run
, nix run
may alternatively detect the executable to run from a “name” or a “meta.mainProgram” field.
Here's an example of calling nix run
with this project:
nix --extra-experimental-features nix-command \
run --file . packages.x86_64-linux.org2gfm -- --help
USAGE: org2gfm [OPTION]... [FILE]...
DESCRIPTION:
Uses Emacs to export Org-mode files to GitHub Flavored
…
This works because the package selected by packages.x86_64-linux.org2gfm
selects a package with name “org2gfm” that is the same as the executable provided at bin/org2gfm
.
If we want something other than what can be detected, then we have to continue using nix shell
with --command
.
The previous sections show how to use nix run
and nix shell
to run commands in an environment that includes packages from a project local to our filesystem.
We can reference remote projects that have a default.nix
file using URLs with the --file
switch. For example, here we reference a tarball of the 24.11 release of Nixpkgs:
nix --extra-experimental-features 'nix-command' \
run \
--file https://github.com/NixOS/nixpkgs/archive/nixos-24.11.tar.gz \
hello
Hello, world!
Downloads from URLs are cached. In case you feel the URL you've downloaded from has changed, use the --refresh
switch with your invocation.
Since we can only specify one --file
switch, we can't make a shell with packages from multiple Nix projects. This is possible with flakes enabled, discussed in the companion usage guide for flakes.
Note the documentation in this project steers people away from nix-shell
, which provides some conveniences at the expense of compromising reproducible builds. Specifically, nix-shell
reads from the NIX_PATH
environment variable. Allowing an environment variable like NIX_PATH
to affect build results has largely been deemed a mistake by the Nix community. Flakes provide the convenience of nix-shell
but with better tracking mutable references called flake registries.
We've seen that we can build programs with nix-build
and execute them using the “result” symlink (result/bin/*
). Additionally, we've seen that you can run programs with nix shell
and nix run
. But these additional steps and switches/arguments can still feel extraneous. It would be nice to have the programs on our PATH
. This is what nix-env
is for.
nix-env
maintains a symlink tree, called a profile, of installed programs. The active profile is pointed to by a symlink at ~/.nix-profile
. By default, this profile points to /nix/var/nix/profiles/per-user/$USER/profile
. But you can point your ~/.nix-profile
to any writable location with the --switch-profile
switch:
nix-env --switch-profile /nix/var/nix/profiles/per-user/$USER/another-profile
This way, you can just put ~/.nix-profile/bin
on your PATH
, and any programs installed in your currently active profile will be available for interactive use or scripts.
To install the org2gfm
executable, which is provided by the packages.x86_64-linux.org2gfm
attribute path, we'd run the following:
nix-env --install --file . --attr packages.x86_64-linux.org2gfm 2>&1
installing 'org2gfm'
building '/nix/store/hs9xz17vlb2m4qn6kxfmccgjq4jyrvqg-user-environment.drv'...
We can see this installation by querying what's been installed:
nix-env --query
org2gfm
Note that this name we see in the results of nix-env
is the package name, not the attribute path we used to select our packages. Sometimes, they are congruent, but not always.
We can see the package name of anything we install by using nix search
with a --json
switch to get more details:
nix --extra-experimental-features nix-command \
search --json --file . '' linux 'org2gfm'
{
"packages.x86_64-linux.org2gfm": {
"description": "Script to export Org-mode files to GitHub Flavored Markdown (GFM)",
"pname": "org2gfm",
"version": ""
}
}
And if we want to uninstall a program from our active profile, we do so by the package's name (“pname” above), in this case “org2gfm”:
nix-env --uninstall org2gfm 2>&1
uninstalling 'org2gfm'
Summarizing what we've done, we've installed our package using its attribute path (packages.x86_64-linux.org2gfm
) within the referenced Nix expression. But we uninstall it using the package name (“org2gfm”), which may not be the same as the attribute path. When a package is installed, Nix keeps no reference to the expression evaluated to obtain the installed package's derivation. The attribute path is only relevant to this expression. In fact, two different expressions could evaluate to the same derivation, but use different attribute paths. This is why we uninstall packages by their package name.
Also, if you look at the symlink-resolved location for your profile, you'll see that Nix retains the symlink trees of previous generations of your profile. You can even roll back to an earlier profile with the --rollback
switch. You can also delete old generations of your profile with the --delete-generations
switch.
Every time you build a new version of your code, it's stored in /nix/store
. There is a command called nix-collect-garbage
that purges unneeded packages. Programs that should not be removed by nix-collect-garbage
can be found by starting with symlinks stored as garbage collection (GC) roots under three locations:
/nix/var/nix/gcroots
/nix/var/nix/profiles
/nix/var/nix/manifests
.
For each package, Nix is aware of all files that reference back to other packages in /nix/store
, whether in text files or binaries. This dependency tracking helps Nix ensure that dependencies of packages reachable from GC roots won't be deleted by garbage collection.
Each “result” symlink created by a nix-build
invocation has a symlink in /nix/var/nix/gcroots/auto
pointing back to it. So we've got symlinks in /nix/var/nix/gcroots/auto
pointing to “result” symlinks in our projects, which then reference the actual built project in /nix/store
. These chains of symlinks prevent packages built by nix-build
from being garbage collected.
If you want a package you've built with nix-build
to be garbage collected, delete the “result” symlink created before calling nix store gc
. Breaking symlink chains under /nix/var/nix/gcroots
removes protection from garbage collection. nix-collect-garbage
will clean up broken symlinks when it runs.
Everything under /nix/var/nix/profiles
is also considered a GC root. This is why users, by convention, use this location to store their Nix profiles with nix-env
.
Also, note that if you delete a “result*” link and call nix-collect-garbage
, though some garbage may be reclaimed, you may find that an old profile keeps the program alive. Use nix-collect-garbage
's --delete-old
switch to delete old profiles (it just calls nix-env --delete-generations
on your behalf).
It's also good to know that nix-collect-garbage
won't delete packages referenced by any running processes. In the case of nix run
no garbage collection root symlink is created under /nix/var/nix/gcroots
, but while nix run
is running nix-collect-garbage
won't delete packages needed by the running command. However, once the nix run
call exits, any packages pulled from a substituter or built locally are candidates for deletion by nix-collect-garbage
. If you called nix run
again after garbage collecting, those packages may be pulled or built again.
This document has covered a fraction of Nix usage, hopefully enough to introduce Nix in the context of this project.
An obvious place to start learning more about Nix is the official documentation.
Bundled with this project is a small tutorial on the Nix language. It's also not bad to know how to use this project with flakes. Flakes are broadly used in the Nix ecosystem, and will become the standard.
All the commands we've covered have more switches and options. See the respective man pages for more.
We didn't cover much of Nixpkgs, the gigantic repository of community-curated Nix expressions.
The Nix ecosystem is vast. This project and documentation illustrate only a small sample of what Nix can do.