Nix as a utilities package manager

Fred ,  
Header

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.

The why and how of Nix

“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:

p2i8g3hcsk-package-1.3.5

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 apt, pacman, or 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

Derivations

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.

What about Docker

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.

Nixery combines Docker with Nix and creates container images with Nix packages; more on that later. In another article we’ll learn how to build Docker containers using Nix.

Installation

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.

Installation

Let’s download and install the Nix package manager.

Tip: install on Ubuntu Multipass or another VM solution to test, delete and purge.

On Linux

# 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

On macOS

# 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!"

Upgrade Nix

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

The main commands in the Nix toolkit

Let’s install some packages. The NixOS Search page is probably the easiest and fastest way to find packages, for example, Neovim. 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 nix-shell instructions.

# switch to the unstable channel
sudo nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
sudo nix-channel --update

Sometimes stable has the latest package even though the website mentions otherwise.

The nix-env command

With the nix-env command you can install, upgrade, and erase packages. You can also query the database to list installed packages.

Nix-env

Install 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

Next, let’s install another handy utility, Tealdeer, a tldr implementation. We can use tldr to list common commands for nix-env.

# 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 packages

# 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

Generations

Let’s say something in your system or application breaks after you update a package. With nix-env --rollback, or nix-env --switch-generation you can easily roll back to a previous state. This is because the Nix store is immutable. 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

With 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.

nix-shell
The command nix-shell will 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/setup has 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 hello package.

# 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-shell feature 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

After you 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.

nix-shell shebangs

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")

A note on Nix and sudo

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 /etc/sudoers:

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 secure_path line. So, append /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 apt, dnf, etc. Or manually install the binaries.

More fun Nix examples

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

The pip command is not available before installing the venv module.

# create a new VirtualEnv
python -m venv .venv
source .venv/bin/activate

# install a package
pip install aws-cli
# done

Install common programming languages with Nix

Up until now, we mainly focused on installing applications. Next, let’s set up some programming languages.

Python

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

Rust

Install Rust with 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, niv, and lorri written by Xe Iaso.

Go

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

NodeJs

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

Using npm install -g <PACKAGENAME> fails.

# globally install nodemon
npm i nodemon -g
npm install nodemon --global

When 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 $HOME directory. You can configure this by setting the NPM prefix key. The prefix directory is the directory that contains a package.json file or node_modules folder. 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

Tools on top of Nix

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.

Nix Flakes

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.

REPLit

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

With Nixery, you can pull a Docker image from the registry and include Nix packages by separating each one with a slash. This talk explains it in more detail.

# 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

Devbox

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

Devshell is a new project that also focuses on simplyfing working with Nix.

Next steps

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 .nix config 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

Learning resources

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.

Comments

Coming soon: "post your comment with support for markdown".

Name Email Reply