Intro to Flakes
Note
Nix is in a period of transition. Commands like nix-shell
, nix-env
and other
commands of the form nix-<smth>
represent the old way of doing things.
TL;DR flakes are the future of nix.
A flake is simply a directory containing at least a flake.nix
file, which describes the flake itself,
and a flake.lock
file, which is a JSON document describing the exact version of every input.
The flake.nix
references one or more inputs (~ dependencies, themselves flakes). nixpkgs
, which
provides the base repository of 80,000 package definitions, is itself a flake.
The flake also describes one or more outputs which it produces or makes available. These can be, but
are not limited to:
- Development Environments
- defines a shell environment with packages, environment variables and automatically run shell hooks
- Flake templates
- akin to project boiler-plates / starter projects in other languages
- Nix packages
- Software and resources to make available for dev environments or direct execution
- NixOS configurations
- declarative configurations of a NixOS machine. Packages, users, network configuration, services enabled etc.
- (Library) functions
- Nix functions exposed to flakes using the flake as an input.
Why use Nix (flakes)?
-
Isolated development environments Flake's can specify one or more development environments. A development environment consists of some number of packages to install and may run hooks on entering the environment to run commands, set environment variables and so on. This allows development on projects without polluting the system's global environment and environments may use different versions of the same librarie(s).
-
Flakes are composable I may write a flake which defines derivations (package build scripts) for one or more pieces of software which are not yet available in NixPkgs, or which are internal to our organization. Your flake may then reference these as inputs and install those packages.
-
Distribution via git - no package repo server/docker registry necessary A flake can be referenced and used directly from a Git repository. This effectively makes it easy to create private "repositories" of customized or internal software without needing to stand up a traditional Debian/Fedora package repository and to configure each client PC to use these repositories.
Our first flake
The following flake (flake.nix
) is truly minimal, it only references the nixpkgs
flake, pinning it to the 22.11 release, and we define no outputs:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11";
};
outputs = { self, nixpkgs}: {
};
}
Pinning nixpkgs
to a fixed release helps us retain reproducibility -
github:NixOS/nixpkgs
points to the master branch, which is a moving target,
while the nixos-22.11
branch is not.
Warning
If you don't specify a release branch for nixpkgs
in your flakes, you
will be pulling from master
, with the flake.lock
file reflecting
the last commit at the time of the lock file's creation.
This means you can still share your flake with others and they will
get the exact same packages as you, but package derivations in master
may not yet have been built by NixOS's build server and cached at cache.nixos.org.
This means your computer would potentially be recompiling a lot of packages.
I tried this with firefox
, which definitely taxed my laptop..!
A 'hello world' flake
- Create a new directory,
hello-world
, and enter into it. - Run
nix flake init
to create a new skeletonflake.nix
file - Add the
inputs
attribute set, defining where to findnixpkgs
and which version to use- Not necessary, but we want good habits!
- Run
nix flake lock
to generate a lock file- Note this would otherwise happen when you first use the flake
Warning
Never commit a flake.nix
file to a git repo without a corresponding
flake.lock
file - the point is to allow your users to replicate what
you have. If you desire randomness and despair, just ship tarballs with
makefiles!
We now have a flake like this one:
{
description = "A very basic flake";
# This we added ourselves, remember..?
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11";
};
outputs = { self, nixpkgs }: {
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
};
}
This flake exposes one program, hello
and makes it possible to run using
nix run
. Let's try running it now:
Note
.#hello - what is that? This is odd at first, but is part of a general concept of how to refer to flakes and outputs within them.
In this case, .
is the current directory, and flakes on the local file
system can be referred to by their path.
The hash #
sign separates the flake reference from the output attribute
in the same way a /
would in an URL. Finally, we refer to the hello
output attribute.
Note
A note on system: this example's package entries start with
packages.x86_64-linux
. The x86_64-linux
-part is called the system.
x86_64-linux
refers to a x86 64bit computer running linux. Similarly,
aarch64-darwin
refers to a 64bit Arm computer running macOS.
As we will see, there are helper functions which allow you to loop over several systems and define the same outputs for all. For now, just be aware of its meaning.