Guides

What is Nix?

A practical introduction to Nix — what it does, why it exists, and the core concepts you need before diving into real projects.

7 min read Nix Beginner Concepts

The problem Nix solves

Software breaks in predictable ways. A project builds on your machine but not on a colleague’s. A CI pipeline produces a different binary than your laptop. An upgrade pulls in a new library version and silently breaks something unrelated. A server that “hasn’t changed” starts behaving differently after an OS update.

These are all symptoms of the same root cause: implicit dependencies. Traditional package managers and build tools leave gaps — they don’t track every input that affects an output. Nix closes those gaps.

What Nix actually is

Nix is three things that share a name:

  • A package manager that installs software in isolation, never overwriting system libraries
  • A build system that produces identical outputs from identical inputs, every time
  • A language (also called Nix) used to describe packages, environments, and system configurations

When people say “Nix,” they usually mean the package manager and build system. When they say “NixOS,” they mean a full Linux distribution built entirely on Nix. You don’t need NixOS to use Nix — it runs on any Linux, macOS, and WSL.

How Nix differs from other tools

If you’ve used package managers or containerisation before, you might wonder where Nix fits in:

ToolWhat it doesHow Nix compares
apt / dnf / HomebrewInstall packages globally, one version at a timeNix installs packages in isolation — multiple versions coexist, nothing is overwritten
DockerBundles an app with its OS into an imageNix builds reproducible artifacts without a container runtime — you can still produce Docker images from Nix, but you don’t need Docker to get reproducibility
asdf / mise / nvmSwitch between tool versions per projectNix does this and manages native libraries, system dependencies, and non-language tooling in the same lockfile
Ansible / ChefConverge a machine toward a desired stateNix declares the exact state — it doesn’t patch in place, it builds a new immutable result and switches to it atomically

Nix isn’t a replacement for all of these — many teams use Docker on top of Nix, for instance. The key difference is that Nix tracks every input, so results are reproducible by construction rather than by convention.

Core concepts

Everything is a derivation

In Nix, a derivation is a recipe for building something — a package, a configuration file, a Docker image, an entire operating system. Every derivation declares its exact inputs: source code, dependencies, build commands. Nix hashes all inputs together to produce a unique store path:

text
/nix/store/a1b2c3d4…-go-1.25.4/

If any input changes — a different source commit, a different compiler version, a different build flag — the hash changes and Nix builds a new, separate output. Nothing is overwritten. This is what makes Nix reproducible and safe to roll back.

The Nix store

All packages live in /nix/store/, each in its own directory named by its content hash. Multiple versions of the same package coexist without conflict. There is no global bin/ or lib/ directory where packages fight over filenames.

terminal
/nix/store/a1b2c3d4…-go-1.25.4/
/nix/store/e5f6a7b8…-go-1.26.1/
/nix/store/c9d0e1f2…-nodejs-24.14.0/

This means installing a new version of Go doesn’t affect any project that depends on the old one. Uninstalling a package is instant — nothing else linked against it.

Flakes

A flake is a standardized way to define a Nix project. It’s a directory with a flake.nix file that declares:

  • Inputs — where to get dependencies (usually nixpkgs, the main Nix package repository with 120,000+ packages)
  • Outputs — what the project produces (dev shells, packages, system configurations, Docker images)

And a flake.lock file that pins every input to an exact revision. This lock file is what guarantees reproducibility — anyone who builds the flake gets the same inputs, therefore the same outputs.

nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { nixpkgs, ... }:
  let
    pkgs = nixpkgs.legacyPackages.aarch64-darwin;  # or x86_64-linux, etc.
  in {
    # A development shell with Python and Node
    devShells.aarch64-darwin.default = pkgs.mkShell {
      packages = [ pkgs.python3 pkgs.nodejs ];
    };
  };
}

Dev shells

The most common entry point to Nix is nix develop. It drops you into a shell with exactly the tools your project declares — no more, no less. Nothing is installed globally. When you exit the shell, the tools are gone from your PATH (but cached in the store for instant reuse).

terminal
$ nix develop
(nix) $ python3 --version
Python 3.13.12
(nix) $ node --version
v24.14.0
(nix) $ exit

$ python3 --version
python3: command not found

Combined with direnv, the shell activates automatically when you cd into the project directory.

What you can do with Nix

Nix scales from a single dev shell to an entire infrastructure:

Use caseWhat Nix provides
Development environmentsPer-project toolchains via nix develop — no version managers, no conflicts
CI/CD pipelinesnix build and nix flake check for hermetic, cacheable builds
Docker imagesMinimal OCI images built from Nix expressions — no Dockerfile needed
Dotfile managementHome Manager declares shell, editor, and tool configs across machines
macOS system managementnix-darwin manages system preferences, Homebrew, and services declaratively
Server infrastructureNixOS defines entire servers as code with atomic upgrades and rollbacks

Keeping the store clean

Because Nix never overwrites — it adds new store paths alongside old ones — the /nix/store/ directory grows over time. Nix provides garbage collection to reclaim space:

terminal
$ nix store gc
1284 store paths deleted, 2.47 GiB freed

This removes any store path that is no longer referenced by a profile, dev shell, or running system. It is always safe to run — Nix will never delete a path that something still depends on.

For more aggressive cleanup you can delete old profile generations first, then garbage-collect:

terminal
$ nix profile wipe-history --older-than 14d   # drop generations older than 2 weeks
$ nix store gc                                # now collect the unreferenced paths

On NixOS and nix-darwin you can automate this with a scheduled garbage collection option so the store stays tidy without manual intervention.

Getting started

Install Nix

terminal
curl -sSf -L https://getnix.io/install | sh -s -- install

This installs the Determinate Nix Installer with flakes enabled by default.

Try a dev shell

Without cloning anything, run a one-off shell with any package:

terminal
$ nix shell nixpkgs#ripgrep nixpkgs#jq
$ rg --version
ripgrep 15.1.0
$ exit  # tools gone from PATH, cached in store

Create your first flake

terminal
mkdir my-project && cd my-project

Create a flake.nix:

nix
{
  description = "My first Nix project";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { nixpkgs, ... }:
  let
    systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" ];
    forAllSystems = nixpkgs.lib.genAttrs systems;
  in {
    devShells = forAllSystems (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      default = pkgs.mkShell {
        packages = with pkgs; [ go gopls ];
      };
    });
  };
}

Then:

terminal
$ git init && git add flake.nix  # flakes require a git repo
$ nix develop
(nix) $ go version
go version go1.26.1 linux/amd64

The first run downloads dependencies and may take a minute. Subsequent runs are instant — everything is cached in the Nix store.

Quick reference

CommandWhat it does
nix developEnter the project’s dev shell
nix shell nixpkgs#<pkg>Temporary shell with a package
nix buildBuild the default package
nix flake checkRun checks (linters, tests, formatting)
nix flake updateUpdate all inputs to latest
nix store gcRemove unused packages from the store

Next steps