nixOS
Thank you to Determinate Systems for the incredible zero-to-nix
guide where this doc was created from
Table of contents
- nixOS
- Table of contents
- nix run
- nix develop
- build packages
- nix search
- flake show
- Code ➡ flake
- Example project
- Reference Section
- Useful Commands
nix run
In Nix, every program is part of a package. Packages can contain multiple programs as well as man pages, configuration files, and more.
echo "Hello Nix" | nix run "nixpkgs#ponysay"
What happened here? The Nix CLI did a few things:
- It used the
nixpkgs
flake reference to pull in some Nix code and targeted theponysay output
(more on this later). - It built the
ponysay
package and stored the result in the Nix store. - It ran the executable at
bin/ponysay
from theponysay
package.
You may have noticed that nix run
doesn't require anything like a nix install
command. This makes it handy for use cases like shell scripting or experimenting with in-progress tools.
nix develop
Development environments help you seal off tools and configuration from the system it is running on. nix develop
activates a development env.
nix develop "github:DeterminateSystems/zero-to-nix#example"
git and curl are now installed as part of the develope environment.
type git
/nix/store/nqdyqplahmhdgz8pzzd5nip17zf3ijzx-git-2.40.1
What happened here? The Nix CLI did a few things:
- It used the
github:DeterminateSystems/zero-to-nix
flake reference to pull in some Nix code and built a specific flake output (more on this later). - It built the packages specified in the environment configuration (again, more on this later).
- It set up an environment with a $PATH that enables the
git
andcurl
packages to be discovered in the Nix store.
Two other things that you can provide in Nix development environments:
-
Although this example doesn't include one, you can define shell hooks, which are arbitrary shell code that runs whenever the environment starts up. Some example use cases for shell hooks:
echo
information about the environment to the console whenever the environment is activated- Run things like checks and linters
- Ensure that other desired hooks, like Git hooks, are properly set up. Run this to see an example shell hook:
1. Nix development environments support environment variables as well. Runnix develop "github:DeterminateSystems/zero-to-nix#hook"
echo $FUNNY_JOKE
to access a (hilarious) value that's available only in the Nix environment. Some example use cases for environment variables: * Set logging levels usingLOG_LEVEL
or whatever is appropriate for the tools you're using. * Set the environment using variables likeNODE_ENV
for Node.js todevelopment
,dev
, and so on.
Run commands inside the development environment
While it's fun to explore the environment, you don't always want to be inside the environment to use it.
The nix develop
command provides a --command
(or -c
) flag that you can use to run commands that use the environment but from your current environment.
Here are some examples for the environment we used earlier:
nix develop "github:DeterminateSystems/zero-to-nix#example" --command git help
Nix development environments and direnv
is a popular tool that automatically loads specific environment variables whenever you cd
into a directory (and then unloads those variables when you cd
out of the directory).
The combination of direnv and Nix can be quite powerful, enabling you to automatically load Nix development environments whenever you navigate to a directory.
For more info, see Effortless dev environments with Nix and direnv on the Determinate Systems blog.
Development env From A Local Flake
Earlier in this guide, we activated a Nix development environment defined in a flake on GitHub. While using an environment in this way is helpful, it's more common to use a development environment defined in a local flake in the current directory.
mkdir nix-javascript-dev && cd nix-javascript-dev
nix flake init --template "github:DeterminateSystems/zero-to-nix#javascript-dev"
Once the template has been initialized, run ls .
to see the contents of the directory, which should include two important files:
- The flake.nix file defines the flake for your project.
- The flake.lock pins all of the flake inputs—essentially the Nix dependencies—in your flake.nix file to specific Git revisions.
- One of the flake outputs of this Nix flake is a development environment for JavaScript. To enter that development environment:
nix develop
Probably not what you expected! What happened here? A few things:
- Nix looked at the devShells flake outputs in flake.nix to figure out which Nix packages to include in the development environment (Nix specifically looked at the packages array).
- Nix built the packages specified under packages and stored them in the Nix store under /nix/store.
build packages
Let's start by building bat
, a syntax-highlighted version of cat written in Rust that has a Nix package defined in Nixpkgs.
mkdir build-nix-package && cd build-nix-package
nix build "nixpkgs#bat"
#bat
indicates that we're building the bat output from the Nixpkgs flake.
Check your results readlink result
after the build.
What's happened here is that the Nix CLI has:
- Downloaded the Nix code in Nixpkgs
- Found a package definition with the name
bat
here - Used the build instructions for
bat
to build the package - Stored the result in the
Nix store
using Nix's hash-based path system.
Build a package for tools written in $LANGUAGE
One of the great things about Nix is that package builds are extremely flexible, which enables you to create packages for things written in just about any programming language. In this section, we'll explore that by building and running packages for tools written in a variety of languages. Select one below to see some examples:
Let's build and run npm
:
nix build "nixpkgs#nodePackages.npm"
./result/bin/npm --help
If you run ls result/bin
you'll notice that the package also includes npx
.
Beyond Nixpkgs
While Nixpkgs
is by far the largest Nix package repository in the known universe, any Nix flake
can include package outputs
. Let's build a package from a different repo, this time the package for Home Manager
, a popular Nix tool for configuring home environments:
nix build "github:nix-community/home-manager"
Here, github:nix-community/home-manager
is a flake reference to the nix-community/home-manager repo on GitHub. To run Home Manager:
./result/bin/home-manager --help
Upstreaming your packages to Nixpkgs is always an option, but it's good to bear in mind that with Nix you can distribute packages via any public Git repository with a flake.nix
.
Build With flake
To get started in your JavaScript project, create an empty directory and initialize a flake template.
mkdir nix-javascript-pkg && cd nix-javascript-pkg
nix flake init --template "github:DeterminateSystems/zero-to-nix#javascript-pkg"
Whichever language you've selected, you can build the Nix package defined in the local flake by running nix build
. This command determines that the local flake has a package output that defines how the package is built. In this particular flake there's a default package, which enables us to run nix build without specifying an output, but if the package were output as packages.mypkg
, for example, we'd need to run nix build .#mypkg
to build it.
Here's the package definition that builds our JavaScript package:
flake.nix
{
description = "JavaScript example flake for Zero to Nix";
inputs = {
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2305.491812.tar.gz";
};
outputs = { self, nixpkgs }:
let
# Systems supported
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
"aarch64-linux" # 64-bit ARM Linux
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
# Helper to provide system-specific attributes
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
pkgs = import nixpkgs { inherit system; };
});
in
{
packages = forAllSystems ({ pkgs }: {
default = pkgs.buildNpmPackage {
name = "zero-to-nix-javascript";
buildInputs = with pkgs; [
nodejs_18
];
src = ./.;
npmDepsHash = "sha256-A85l8kFgIU2grgDQNBM7ilLVPehMl6ilkpt4YoiZyeo=";
npmBuild = "npm run build";
installPhase = ''
mkdir $out
cp dist/index.html $out
'';
};
});
};
}
What you see here is a derivation that defines how to build the package, more specifically the buildNpmPackage function, which is a wrapper around Nix's built-in derivation function.
The package that results when you run nix build is a website built using the Vite framework. To view that website, open the HTML file at result/index.html.
nix search
Search nixpkgs with nix search nixpkgs vim
. In this command, the nixpkgs flake reference is shorthand for github:NixOS/nixpkgs
. You can output to json if you'd like nix search nixpkgs vim --json
.
The web interface at search.nixos.org has a few advantages over the nix search
command:
- It enables you to select a release channel for Nixpkgs, such as
22.11
andunstable
- It enables you to search across a range of
public flakes
beyond Nixpkgs. Those flakes are listed here.
flake show
Should you use nix flake show or nix search? A good rule of thumb is to always use nix search with Nixpkgs and to initially use nix flake show with other flakes. If the package outputs for nix flake show are big enough to be tricky to navigate, use nix search for that flake instead.
nix flake show github:nix-community/nixpkgs-wayland
One thing you'll notice about the search output for nix search, search.nixos.org, and nix flake show is that all the packages listed in the query results are for your current system (x86_64-linux for an AMD/Intel Linux system, aarch64-darwin for an Apple Silicon system, and so on). That's because Nix works in a fundamentally system-specific way. The cargo package on a Linux machine is considered a different package from cargo on a non-Linux system.
Code ➡ flake
Turning your own projects into flakes can be somewhat tricky, so Determinate Systems have created a tool that can help in many scenarios: fh
, the CLI for the FlakeHub platform.
fh
has a utility called fh init
that creates a flake.nix
file based on two things:
- The contents of your project
- Your responses to its interactive questions
You can run fh init
using Nix:
nix run "https://flakehub.com/f/DeterminateSystems/fh/*.tar.gz" -- init
This will start up an interactive builder that asks you a series of questions and then writes a flake.nix
file into the root of your project (plus some other files if you say yes to some of those questions).
Once you've generated a new flake, you can see which outputs it has:
nix flake show
You should see something like this:
git+file:///path/to/fh-init-example-project
├───devShells
│ ├───aarch64-darwin
│ │ └───default: development environment 'nix-shell'
│ ├───aarch64-linux
│ │ └───default omitted (use '--all-systems' to show)
│ ├───x86_64-darwin
│ │ └───default omitted (use '--all-systems' to show)
│ └───x86_64-linux
│ └───default omitted (use '--all-systems' to show)
└───schemas: unknown
fh init
supports a wide variety of languages and tools.
If your project has a Cargo.toml
file in the root, for example, then fh init
infers that it's a Rust project and asks if you want to add Rust dependencies to your Nix development environment.
If you say yes, then the generated flake.nix
will include the cargo
build tool plus some other Rust-specific tools.
Note that fh init
currently only supports devShells
outputs.
That is, it only generates a development environment for you, not things like package outputs.
The limitations of fh init
Be aware that fh init
operates on a "best-guess" basis to infer which languages and tools you use in your project.
It's possible that it will miss things or make incorrect guesses.
But we hope that the flake.nix
that it creates for you will at least serve as a solid initial template that you can modify further.
Example project
We've created an example project that you can use to test out fh init
:
git clone https://github.com/DeterminateSystems/fh-init-example-project
cd fh-init-example-project
nix run "https://flakehub.com/f/DeterminateSystems/fh/*.tar.gz" -- init
# respond to the prompts
nix flake show
Reference Section
Packages - Package Management - Nix Packages - Nix Store - Derivation
Flakes - FlakeHub - Flake Reference - Flake Input - Flake Output
Useful Commands
Garbage collection either works
See more here
nix-collect-garbage
nix-store --gc
nix shell "github:NixOS/nix/2.18.1"
nix shell "github:NixOS/nix/2.18.1"
Nix cheat sheet
nix-env
package management
action | Ubuntu | Nix | notes |
---|---|---|---|
update package list | sudo apt update |
sudo nix-channel --update |
|
search | apt search <query> |
nix search <query> |
also try search.nixos.org/packages |
install | sudo apt install <package> |
nix-env -i <package> |
no root, atomic, per user |
upgrade installed | sudo apt upgrade |
nix-env -u |
no root, atomic, per user |
remove | sudo apt remove <package> |
nix-env -e <package> |
no root, atomic, per user |
undo last operation | ... | nix-env --rollback |
no root, atomic, per user |
list installed | dpkg -l |
nix-env -q |
per user |
show generations | ... | nix-env --list-generations |
nix-shell
isolated build/dev/run environments
command | result |
---|---|
nix-shell -p <packages> |
start shell in env with <packages> |
nix-shell |
start shell in the env defined by shell.nix or default.nix in current dir |
nix-shell --pure |
same, but outside env is inaccessible |
NixOS declarative operating system configuration management
command | result | notes |
---|---|---|
edit /etc/nixos/configuration.nix |
define new system configuration | running system is unaffected |
nixos-rebuild switch |
switch to the configuration defined in /etc/nixos/configuration.nix |
atomic* |
nixos-rebuild switch --rollback |
switch to previous configuration | atomic* |
nixos-option <option> |
show option value and documentation | also try search.nixos.org/options |