Nix flakes

A system for referencing and sharing Nix code

Concepts / Nix flakes
Flakes are experimental but nonetheless strongly recommended

Nix flakes are currently an experimental feature in Nix and there is currently no specific timeline for making flakes official. While the user interface around flakes is unlikely to change drastically while they remain experimental, there may be breaking changes along the way.

Channels will continue be the "official" way of using Nix for the foreseeable future. We strongly recommend, however, that you learn to use flakes if you're already a Nix user or to begin your Nix journey with flakes rather than channels if you're just getting started with Nix.

A Nix flake is a directory with a flake.nix and flake.lock at the root that outputs Nix expressions that others can use to do things like build packages, run programs, use development environments, or stand up NixOS systems. If necessary, flakes can use the outputs of other flakes as inputs.

Mental model

It may be helpful to think of flakes as processors of Nix code. They take Nix expressions as input and output things that Nix can use, like package definitions, development environments, or NixOS configurations.

Flakes thus form a kind of chain. Let's say that I create a flake that uses a helper function output by Nixpkgs—which is a flake!—to define a package build. My teammate could then use the package definition from my flake as part of a Nix development environment. Another team then could use that development environment in one of their projects. And so on.

Flake references

A flake reference is a string representation of where the flake is located. Flake references are used in two places:

  1. In flake input declarations to depend on outputs from the flake.
  2. In shell environments when running commands like nix run github:DeterminateSystems/riff (which runs the Riff program).

Here are some example flake references:

Reference Description
path:/home/nix-stuff/my-flake The /home/nix-stuff/my-flake directory on the current host
github:DeterminateSystems/zero-to-nix The DeterminateSystems/zero-to-nix GitHub repository
github:DeterminateSystems/zero-to-nix/other The other branch of the DeterminateSystems/zero-to-nix GitHub repository
github:DeterminateSystems/zero-to-nix/d51c83a5d206e882a6f15a282e32b7079f5b6d76 Commit d51c83a5d206e882a6f15a282e32b7079f5b6d76 on the DeterminateSystems/zero-to-nix GitHub repository
nixpkgs The most recent revision of the nixpkgs-unstable branch of Nixpkgs (an alias for github:NixOS/nixpkgs)
nixpkgs/release-22.11 The release-22.11 branch of Nixpkgs
https://flakehub.com/f/NixOS/nixpkgs/0.2305.*.tar.gz The most recent revision of the nixos-23.05 branch of Nixpkgs hosted on FlakeHub

You can find a more systematic treatment of flake references in the official documentation.

References and revisions

Flake references are always to a specific revision of the flake. Nixpkgs, for example, is a flake but each revision of Nixpkgs—and there are many thousands—has a flake reference.

Flake inputs

Flake inputs are Nix dependencies that a flake needs to be built. Each input in the set can be pulled from various sources, such as github, generic git repositories, and even your filesystem.

Furthermore, inputs can modify each other's inputs to make sure that, for example, multiple dependencies all rely on the same version of nixpkgs. This is done via the inputs.<input>.follows attribute.

flake.nix{
  inputs = {
    nixpkgs = "github:NixOS/nixpkgs";
  };
}

You can find a full breakdown of the flake input schema in the Nix manual.

The flake.lock file

All flake inputs are pinned to specific revisions in a lockfile called flake.lock. This file stores revision information as JSON.

The flake.lock file ensures that Nix flakes have purely deterministic outputs. A flake.nix file without an accompanying flake.lock should be considered incomplete and a kind of proto-flake. Any Nix CLI command that is run against the flake—like nix build, nix develop, or even nix flake show—generates a flake.lock for you.

Here's an example section of a flake.lock file that pins Nixpkgs to a specific revision:

flake.lock{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1668703332,
        // A SHA of the contents of the flake
        "narHash": "sha256-PW3vz3ODXaInogvp2IQyDG9lnwmGlf07A6OEeA1Q7sM=",
        // The GitHub org
        "owner": "NixOS",
        // The GitHub repo
        "repo": "nixpkgs",
        // The specific revision
        "rev": "de60d387a0e5737375ee61848872b1c8353f945e",
        // The type of input
        "type": "github"
      }
    },
    // Other inputs
  }
}

If this flake.lock were alongside a flake.nix with this input block...

flake.nix{
  inputs = {
    nixpkgs.url = "nixpkgs";
  };

  outputs = { self, nixpkgs }: {
    # Define outputs here
  };
}

...the nixpkgs attribute would be implicitly pinned to the de60d387a0e5737375ee61848872b1c8353f945e revision—even though that revision information isn't in Nix code itself.

Flake registries

Flake registries are a convenience feature that enables you to refer to flakes using symbolic identifiers rather than full flake references. The most widely used symbolic identifier is nixpkgs, which is an alias for the github:NixOS/nixpkgs/nixpkgs-unstable flake reference

Some symbolic identifiers that you may encounter:

Symbolic identifier Full flake reference
nixpkgs github:NixOS/nixpkgs/nixpkgs-unstable
flake-utils github:numtide/flake-utils
home-manager github:nix-community/home-manager

The full default global flake registry is kept as a JSON file.

Here's an example flake that uses symbolic identifiers:

flake.nix{
  outputs = { self, nixpkgs, flake-utils }:
    let
      pkgs = import nixpkgs { inherit system; };
    in flake-utils.lib.eachDefaultSystem (system: {
      devShells.default = pkgs.mkShell {
        packages = with pkgs; [ curl git jq wget ];
      };
    });
}

Note that the inputs block has been omitted in this flake. Using flake registries is always optional.

Flake outputs

Flake outputs are what a flake produces as part of its build. Each flake can have many different outputs simultaneously, including but not limited to:

Flake outputs are defined by a function, which takes an attribute set as input, containing each of the inputs to that flake (named after the chosen identifier in the inputs section).

Exporting functions

In addition to things like packages and NixOS configurations, flakes can also output Nix functions for use elsewhere. Nixpkgs, for example, outputs many helper functions via the lib attribute.

The lib convention

The convention of using lib to output functions is observed not just by Nixpkgs but by many other Nix projects. You're free, however, to output functions via whichever attribute you prefer.

Here's an example flake that outputs a sayHello function, via the lib attribute, that takes a name as an input and outputs a string saying hello to a person with that name:

flake.nix{
  outputs = { self }: {
    lib = {
      sayHello = name: "Hello there, ${name}!";
    };
  };
}

Another Nix flake could then specify this flake as an input and use sayHello for whatever purpose.

System specificity

Some flake outputs need to be system specific, including packages, development environments, and NixOS configurations. Here's an example flake that outputs a package that can be used by x86_64-linux systems (64-bit AMD/Intel Linux):

flake.nix{
  outputs = { self, nixpkgs }: let
    # Declare the system
    system = "x86_64-linux";
    # Use a system-specific version of Nixpkgs
    pkgs = import nixpkgs { inherit system; };
  in {
    # Output `ponysay` as the default package of the flake
    packages.${system}.default = pkgs.ponysay;
  };
}

In many cases, however, you'll need to output things like packages or development environments for multiple systems. Helper libraries like flake-utils provide convenient mechanisms for doing that. You can also use Nix functions like this:

flake.nix{
  outputs = { self, nixpkgs }: let
    # The set of systems to provide outputs for
    allSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];

    # A function that provides a system-specific Nixpkgs for the desired systems
    forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
      pkgs = import nixpkgs { inherit system; };
    });
  in {
    packages = forAllSystems ({ pkgs }: {
      default = {
        # Package definition
      };
    });
  };
}

Flake templates

Flake templates enable you to either initialize a new Nix project with pre-supplied content or add a set of files to an existing project. You can initialize a flake template using the nix flake init command. Flakes can output templates using the templates attribute. Here's an example:

flake.nix{
  outputs = { self }: {
    templates = {
      starter-template = {
        path = ./my-starter-template;
        description = "A getting started template for a new Nix project";
      };
    };
  };
}

If you ran nix flake init --template <reference> against this template definition, Nix would copy the contents of the ./my-starter-template directory into the current directory (without overwriting existing files).


Was this page helpful?