nixpkgs

Prerequisites

What it is

nixpkgs is a Git repository (https://github.com/NixOS/nixpkgs) that contains:

  • ~100,000+ package definitions (as of 2024, the largest package repository of any Linux distribution)
  • The entire NixOS module library (all services.*.enable options)
  • stdenv and build helpers (the standard build environment)
  • lib — a large library of utility functions used by both packages and modules

It is itself a Nix expression: the top-level file is a function that takes a configuration and returns an attribute set containing all packages:

# Conceptually (simplified):
{ system ? "x86_64-linux", overlays ? [], config ? {}, ... }:
{
  hello = import ./pkgs/applications/misc/hello { inherit stdenv fetchurl; };
  git   = import ./pkgs/applications/version-management/git { ... };
  python311 = import ./pkgs/development/interpreters/python/3.11 { ... };
  python311Packages.requests = import ... { ... };
  # ... ~100,000 more
}

How you consume it

There are two approaches, corresponding to different eras of Nix:

Channels (legacy, pre-flakes)

# Subscribe to a channel (a URL pointing to a nixpkgs revision)
nix-channel --add https://nixos.org/channels/nixos-25.05 nixos
nix-channel --update   # fetches whatever the channel currently points to
 
# Use in Nix code
import <nixpkgs> {}    # <nixpkgs> resolves to the channel path

The version you get depends on when you last ran nix-channel --update. There’s no pinning in the expression itself — two people can have different channel states and get different builds from the same .nix file.

# flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
  outputs = { nixpkgs, ... }: { ... };
}

The exact commit is recorded in flake.lock. Anyone with the same lockfile evaluates against the same nixpkgs revision. See Nix Flakes for the full mechanism.

Branch structure

nixpkgs has several important branches:

BranchWhat it is
nixos-25.05 (example)Stable release branch. Receives security patches and critical fixes only. New NixOS versions release roughly every 6 months (May and November).
nixos-unstableRolling release. Packages are updated continuously. Must pass Hydra (the NixOS CI system) tests before landing.
nixpkgs-25.05-darwinStable branch for macOS (nix-darwin).
masterDevelopment branch. PRs land here first. Not tested as a whole.

The pattern: use a stable branch for your system’s base, pull specific packages from nixos-unstable when you need newer versions.

legacyPackages vs packages in flakes

Flake outputs have a schema where packages.<system>.<name> must be a flat set of derivations. But nixpkgs is deeply nested:

pkgs.python311Packages.requests
pkgs.haskellPackages.lens
pkgs.nodePackages.typescript

These nested package sets can’t be enumerated as a flat packages output (and many internal attributes aren’t derivations at all).

legacyPackages is a flake output type that says “this is a nixpkgs-style nested package set; don’t try to validate or enumerate it as a flat packages output.” The name is misleading — there’s nothing deprecated about the packages themselves. It’s just a schema escape hatch.

In practice, you consume nixpkgs in a flake via:

# Access the full nested package set
pkgs = nixpkgs.legacyPackages.${system};
 
# Then use it normally
pkgs.hello
pkgs.python311Packages.numpy

NixOS modules live here too

nixpkgs isn’t just packages. The directory nixos/modules/ contains the entire NixOS module library — every option you set in configuration.nix is defined there. When you write services.openssh.enable = true, the module at nixos/modules/services/networking/ssh/sshd.nix in nixpkgs defines what that option does.

This is why your nixpkgs input version matters for NixOS: it determines both which package versions are available AND which configuration options exist.

See also