Nix, the purely functional OS package manager, is a powerful beast that can be difficult to tame. At the same time, it’s easy to use Nix as a simple platform independent utility installer. For example, in this article, we’ll use Nix to install your day-to-day toolbox of command-line apps and programming languages.
⚠️ Note: this is an introduction to the world of Nix from the perspective of a new user.
“Reproducible builds and deployments.” That’s the headline on the Nix website. To understand the problem that Nix solves1, we need to be introduced to some of Nix’s vocabulary.
- From ReproducibleBuilds.org:
- “A build is reproducible if given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts.”
Purely functional and reproducible
Let’s start with one of the essential concepts: reproducible builds. It refers to how packages are built by a pure function, which always produces the same output for the same input. In other words, if the input of the build process does not change, then the output does not change. So if the source code, input packages, dependencies, environment, configuration, and the build instructions do not change, then the output is the same on every build, although not (yet) a hundred-percent bit-by-bit.
Packages can depend on other packages, which creates a large dependency graph. Each Nix package has a unique identifier – a kind of fingerprint – that captures all those dependencies. Specifically, it’s a cryptographic hash of that package’s build dependency graph. For example:
Packages are built in a declarative and isolated way. Only explicitly declared dependencies are used during the build process. This ensures that package dependency specifications are always complete.
Compared to other system package managers, this approach means that Nix can guarantee that a build is always reproducible. Today, and many years from now.
The Nix store
Nix stores packages in the Nix store, where each package has a unique subdirectory identified by its fingerprint (or ID) and the package name. Therefore, different versions of the same package can be installed side by side and without conflicts because each version has its unique ID. This differs from package managers like
dnf, where shared dependencies can create bugs due to version issues. Even the order of installation has the potential to create conflicts.
Furthermore, the Nix store is immutable, so when a problem does occur, perhaps after updating a package, it’s easy to roll back to a previous state. The combination of immutability, the uniquely addressed store, and the pure build process can help reduce software conflicts.
If this exploration page of Nix can convince you to try it out, then the easiest way to experience Nix is to create a Replit. Since 2021, REPLit has used Nix to create cloud-based development environments in your browser.
# run Neovim from a Nix Replit nix-env -iA nixpkgs.neovim nvim
The next vocabulary word, derivation, is another word you will read about sooner or later. A derivation is a build action. It specifies all the inputs of a build environment. If you build the derivation – with the specified builder – you can output a package to the Nix store. Creating our own derivations is a subject for a future article.
So Nix is a functional and declarative approach to building reproducible packages and development environments. This sounds a lot like Docker. The two are different but do overlap.
Both tools help teams to easily create reproducible development environments. One of the differences is that Docker doesn’t have the same reproducibility guarantees as Nix. Also, Docker mainly solves a different problem. It’s a toolkit for building and deploying containers. The focus of containers is isolating a process and its resources from the operating system. It’s not about package management.
Nix works on macOS and many Linux flavors, including WSL on Windows. Therefore you could use Nix as an installer for your favorite utilities, the primary use case in this article. Porting over Nix system installation scripts from Ubuntu to Fedora, Arch, WSL or macOS should be easy and makes Nix an attractive universal package management solution.
The large package repo, currently 80.000 packages, has up-to-date versions but also contains non-default packages such as
lazygit. On Ubuntu, you would have to add a PPA that contains an older version, while Nix has the latest version by default. The same is true for Neovim and others. This a positive detail if you like to work on the bleeding edge.
Single-user vs. multi-user install
You have two installation options, “single-” or “multi-user”. A single-user install is easy to remove, so probably the best option if you want to try Nix. A multi-user install creates extra users and a service for the Nix daemon. This is the only install option on macOS.
Nix advises a multi-user installation, but the main website is somewhat vague about the differences. The security chapter of the manual states that a multi-user install can safely share a Nix store among users. On single-user machines, a single-user install works perfectly fine.
Let’s download and install the Nix package manager.
Tip: install on Ubuntu Multipass or another VM solution to test, delete and purge.
# default multi-user install sh <(curl -L https://nixos.org/nix/install) --daemon # follow the instructions # single-user install sh <(curl -L https://nixos.org/nix/install) --no-daemon # source the config . /home/ubuntu/.nix-profile/etc/profile.d/nix.sh
# install multi-user Nix sh <(curl -L https://nixos.org/nix/install)
On Windows (WSL2/Ubuntu)
# make sure WSL2 is installed # check wsl version (from a Windows command prompt) wsl --list --verbose # check kernel number, WSL2 is kernel v4.19 and higher uname -r # install single-user Nix sh <(curl -L https://nixos.org/nix/install) --no-daemon
Verify the installation
After installation, ensure that Nix is installed correctly by testing the commands.
# versions nix --version nix-env --version # install the hello world package nix-env -i hello which hello # output "/home/fred/.nix-profile/bin/hello" hello # output: "Hello, world!"
The manual explains how to
upgrade the different installations. For a single-user upgrade, you can run:
nix-channel --update; nix-env -iA nixpkgs.nix Run the following command to check if the current install is single or multi.
nix-shell -p nix-info --run "nix-info -m" # Nix must be installed
Let’s install some packages.
NixOS Search page is probably the easiest and fastest way to find packages,
You can choose to search in the stable or unstable channel.
In the case of Neovim, the current latest version (v0.8.0) is part of unstable.
As a nice gesture to new users: every package on the website includes install and
# switch to the unstable channel sudo nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable sudo nix-channel --update
stablehas the latest package even though the website mentions otherwise.
The nix-env command
command you can install, upgrade, and erase packages. You can also query the database to list installed packages.
Let’s install Neovim.
# install into your environment nix-env -iA nixpkgs.neovim # open Neovim nvim # you can also install packages with # nix-env -i neovim # however, some packages can only be found by attribute
# install packages nix-env --install --attr nixpkgs.tealdeer nix-env -iA nixpkgs.tealdeer # short form flags # you can also "dry run" the install nix-env --install -A nixpkgs.tealdeer --dry-run # download the tldr database tldr --update # list the tldr info for the nix-env command tldr nix-env
Last but not least, install
bat, and some other tools in a one-liner. The
bat command is a replacement for
cat, which adds modern features like syntax highlighting.
# install bat nix-env -iA nixpkgs.bat # install multiple packages in one command nix-env -iA nixpkgs.starship nixpkgs.ripgrep nixpkgs.tree
Query the database and list installed packages.
# list all installed packages nix-env -q nix-env --query nix-env -q --installed # install fzf nix-env -iA nixpkgs.fzf # and fuzzy search your installed packages nix-env -qa | fzf
# upgrade everything nix-env --upgrade nix-env -u # upgrade a specific package nix-env --upgrade <PACKAGE_NAMES>
Erase an installed package
# uninstall a package nix-env --uninstall firefox # erase a package, same as uninstall nix-env -e tree nix-env -e tree ripgrep # erase multiple packages nix-env -e '.*' # erase everything # delete all packages that are not in use nix-collect-garbage # for example, packages installed with nix-shell
Let’s say something in your system or application breaks after you update a package.
nix-env --rollback, or
nix-env --switch-generation you can easily roll back to a previous state.
This is because the Nix store is
This means that on every store modification (installing, upgrading, or deleting packages), the store’s state is saved as a generation.
# list all generations nix-env --list-generations # switch to a specific generation nix-env --switch-generation <GENERATION_NUMBER> nix-env -G <GENERATION_NUMBER> nix-env -G 31 # switch to 1 generation back nix-env --rollback
Search packages on the terminal
Besides using the Nix Search page, you can also
grep for packages on the command line by querying all available packages and outputting the result to a file.
📌 Note: downloading all packages may take some time, and the download process crashes on low-memory machines.
# list and store all available nix packages nix-env --query --available > nix-packages.list nix-env -qa > nix-packages.list nix-env -qaP > nix-packages.list # list packages with attribute paths # search for available python packages cat nix-packages.list | grep python # count all packages wc --lines nix-packages.list wc -l nix-packages.list
The nix-shell command
nix-shell, you have a quick and easy way to run any software package without installing it in your environment.
The manual describes it as: “start an interactive shell based on a Nix expression.”
The packages installed in a temporary
nix-shell are only part of that (disposable) shell.
As a result,
nix-shell packages don’t “pollute” your machine with installed packages you only need once.
- The command
nix-shellwill build the dependencies of the specified derivation, but not the derivation itself. It will then start an interactive shell in which all environment variables defined by the derivation path have been set to their corresponding values, and the script
$stdenv/setuphas been sourced. This is useful for reproducing the environment of a derivation for development.
Let’s demonstrate the power of an interactive shell by installing the utterly useless
# test the hello package nix-shell -p hello hello # Hello, world! exit # exit the interactive shell hello # 'hello' not found # now let's create a disposable shell with Neovim nix-shell -p neovim nvim # opens Neovim exit nvim # not found
Or, create a disposable Clojure environment to quickly test some code.
These one-off development shells are ephemeral because of their short lives. But remember that they can leave artifacts on your system, such as configuration files. Use a container, or VM, for complete isolation.
📌 Note: the
nix-shellfeature is not about process isolation, so modifications to the filesystem are persistent.
nix-shell -p clojure clojure # run Clojure # build your program exit # exit and discard # now, try to run Clojure again clojure
exit an interactive Nix shell, the packages remain in the Nix store. So re-creating the same shell is a quick operation. To clean up packages, you can garbage collect with the
nix store gc command.
A use case worth mentioning: it’s possible to use
nix-shell as a script interpreter.
Read more about that feature in the Nix wiki.
#! /usr/bin/env nix-shell #! nix-shell -i python3 print("hello world")
After installing a package with Nix on a Linux system (Ubuntu/Debian), it could be that
sudo does not work.
sudo: nvim :command not found
A quick and dirty workaround is to use the following syntax:
# nix sudo command error sudo nvim textfile.md # stdout >> sudo: nvim command not found # use curly brace variable interpolation sudo $(which nvim) textfile.md # or set the sudo PATH environment variable to the value of the current user sudo env "PATH=$PATH" nvim textfile.md
A permanent and more convenient
solution is to modify
which nvim # /home/ubuntu/.nix-profile/bin/nvim # edit the sudoers sudo visudo /etc/sudoers
Add the path pointing to the Nix binaries to the
/home/<YOUR_USERNAME>/.nix-profile/bin, the line will than like look this:
/etc/sudoers Defaults secure_path="/usr/local/sbin:/etc:/etc:/home/ubuntu/.nix-profile/bin"
A third option is to install a “sudo-app” with
dnf, etc. Or manually install the binaries.
Try the Fish shell
Let’s see if we like the Fish shell with the Starship prompt.
# create an interactive shell with Fish nix-shell -p fish fish # change shell # add Starship prompt to interactive shell nix-env -iA nixpkgs.starship # initialize starship starship init fish | source
⚠️ Remember that changes to the filesystem are persistent, so the Fish config files stored in
~/.config/fish/are not removed from your user profile. With Docker you can create an isolated filesystem.
Create a quick Python AWS shell with Nix
The AWS CLI is an example of a package that uses Python 3. Setting up a quick Python environment is a no-brainer.
# create an interactive shell with the Python package nix-shell -p python3 python --version # verify
pip command is not available before installing the
# create a new VirtualEnv python -m venv .venv source .venv/bin/activate # install a package pip install aws-cli # done
Up until now, we mainly focused on installing applications. Next, let’s set up some programming languages.
Install the latest Python.
# install python nix-env -iA nixpkgs.python3 python --version # create a new VirtualEnv python -m venv .venv source .venv/bin/activate # install packages pip install --upgrade pip pip install requests beautifulsoup4 # etc
rustup from nixpkgs.
# install with rustup nix-env -iA nixpkgs.rustup rustup update stable # verify rustc --version cargo --version # test with: https://github.com/ogham/dog # (named nixpkgs.dogdns in Nix) cargo install dog # you can also launch a minimal interactive shell with only cargo and the Rust compiler nix-shell -p rustc cargo
Rust developers: check out this guide on how to
build a Rust app with Nix,
lorri written by Xe Iaso.
Install the latest Go version.
# install Go / Golang nix-env -iA nixpkgs.go # note: go is an example of a package you can only install by it's attribute # note: `nix-env -i go` # does not work go version
Install the latest Node.js version.
nix-env -iA nixpkgs.nodejs nix-env -iA nixpkgs.nodejs-14_x # install v14.x.x node --version
Global Node packages
npm install -g <PACKAGENAME> fails.
# globally install nodemon npm i nodemon -g npm install nodemon --global
npm installs a global package, it will try to use the
node root folder.
Because the Nix store (
/nix/store/) is read-only, the global install will fail.
One of the solutions is to install global packages in the user’s
You can configure this by setting the NPM
prefix directory is the directory that contains a
package.json file or
The global prefix value is where global packages are installed.
# list all npm config key-value's # and grep for prefix npm config ls -l | grep prefix # or, use npm to print the global prefix npm prefix -g # output: prefix = "/nix/store/nd5clq0hw8cpb73b7fij742633wj9ni8-nodejs-14.17.6" # create a directory for the global packages mkdir ~/.npm-global # set prefix with npm npm set prefix ~/.npm-global # ammending your $PATH # add to .bashrc (or other shell) export PATH=$PATH:~/.npm-global/bin # load new bash configuration source ~/.bashrc
The syntax of the Nix expression language is similar to a functional language like Haskell.
So, for C-flavoured programmers, it may be challenging to dive into.
You need to write
.nix to create your own derivations and configure environments; this might be a roadblock.
Thankfully, nowadays, some projects try to simplify working with Nix and use it as a foundation.
Before diving into third-party tools, Nix Flakes, is an upcoming feature that simplifies working with Nix. We’ll look into Flakes in an upcoming article. It’s said that Flakes is the future of Nix, so check out this article if you can’t wait.
We already mentioned Replit, an easy way to see Nix in action. They use it to create environments in the cloud. If you want to create environments locally. You can try one of the following tools.
Nixery: OCI container images combined with Nix
# create empty interactive shell nix-shell -p # you can install Docker with Nix nix-env -iA nixpkgs.docker # and manually run the docker daemon sudo dockerd & # run in the background # press enter to return to the prompt # verify it's running docker info # this gives you an image with git, htop and an interactively configured shell docker pull nixery.dev/shell/git/htop # you could run it like this: docker run -ti nixery.dev/shell/git/htop bash
With Devbox, you get instant, easy, predictable shells and containers. It’s really as easy as:
# install Nix # install Docker curl -fsSL https://get.jetpack.io/devbox | bash mkdir newproject devbox init # this will create a devbox.json file devbox add python310 # add a Nix package devbox shell # build a container image devbox build
A nice detail is that your shell prompt is forwarded to the Nix shell.
Devshell is a new project that also focuses on simplyfing working with Nix.
We used Nix as a basic package manager and showed how to create disposable shells. Quickly dropping into a shell without affecting your main system environment is undoubtedly powerful.
But there is more; in an upcoming article, we’ll dive into the following topics:
- create your own derivations
- configure your own dev-environments
- share environments by adding a
.nixconfig file to your repo
- automate switching development environments with lorri, direnv, and home-manager
- use your
zsh-prompt in an interactive shell with the zsh-nix-shell project
To learn more, check out the following learning resources. The learn section of the Nix homepage is a great place to learn more about Nix. There is, for example, the How Nix Works guide. There you can also find the NixOS Wiki, and articles like Building and running Docker images. Nix Pills is like “the book” for Nix, an excellent introductory tutorial series.
Besides the official learning resources, there are many more other fantastic efforts that help others learn Nix. Domen Kožar, for example, created nix.dev, an opinionated guide for developers getting things done using the Nix ecosystem. Some great people from inria.fr created a tutorial series that demonstrates the power of Nix. Nicolas Mattia, wrote a guide on how to set up a custom declarative config. Michael Maclan often writes about Nix. And Burke Libbey created a video series called Nixology, a great introduction to Nix.