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.
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.
A flake reference is a string representation of where the flake is located. Flake references are used in two places:
nix run github:DeterminateSystems/flake-checker
(which runs the flake-checker 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.2405.* |
The most recent revision of the nixos-24.05 branch of Nixpkgs hosted on FlakeHub |
You can find a more systematic treatment of flake references in the official documentation.
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 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.url = "github:NixOS/nixpkgs";
};
}
You can find a full breakdown of the flake input schema in the Nix manual.
flake.lock
fileAll 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 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 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).
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.
lib
conventionThe 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.
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 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).