Skip to content

nixOS

Thank you to Determinate Systems for the incredible zero-to-nix guide where this doc was created from

Table of contents

reference section


Learn More

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 the ponysay 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 the ponysay 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 and curl packages to be discovered in the Nix store.

Two other things that you can provide in Nix development environments:

  1. 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:

    nix develop "github:DeterminateSystems/zero-to-nix#hook"
    
    1. Nix development environments support environment variables as well. Run 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 using LOG_LEVEL or whatever is appropriate for the tools you're using. * Set the environment using variables like NODE_ENV for Node.js to development, 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"
Here, nixpkgs is a flake reference to the NixOS/nixpkgs repository on GitHub, while #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.

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 and unstable
  • 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:

  1. The contents of your project
  2. 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
Change nix-shell versions

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