9 KiB
title | date | lastmod | draft | keywords | description | tags | categories | author | comment | toc | autoCollapseToc | postMetaInFooter | hiddenFromHomePage | contentCopyright | reward | mathjax | mathjaxEnableSingleDollar | mathjaxEnableAutoNumber | hideHeaderAndFooter | flowchartDiagrams | sequenceDiagrams | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Nix Flakes - A Way to Manage Nix Projects | 2023-06-18T12:12:38+05:30 | 2023-06-18T12:12:38+05:30 | false |
|
|
|
false | true | false | true | false | false | false | false | false | false | false |
|
|
Nix is a powerful package manager that allows you to build, run, and deploy software in a reproducible and declarative way. However, until recently, Nix had some limitations when it came to managing dependencies, composing projects, and delivering software to users. In this blog post, we will introduce nix flakes, a new experimental feature that aims to solve these problems and improve the usability and composability of Nix.
Nix flakes explained
A flake is a source tree (such as a Git repository) that contains a file named flake.nix
in its root directory. This file provides a standardized interface to Nix artifacts such as packages, NixOS modules, or Nix functions. A flake can have dependencies on other flakes, which are specified using a URL-like syntax. For example, github:NixOS/nixpkgs
refers to the nixpkgs flake hosted on GitHub.
A flake also has a lock file named flake.lock
that pins the exact revisions of its dependencies to ensure reproducible evaluation. The lock file can be updated programmatically using the nix flake update
command.
Flakes are evaluated in a hermetic way, meaning that they don't depend on any external factors such as environment variables, command-line arguments, or the system type. This makes them more reliable and portable across different platforms and environments.
Using nix flakes
To use nix flakes, you need to install an experimental version of Nix that supports them. You can get it from nixpkgs by running:
nix-shell -I nixpkgs=channel:nixos-23.05 --packages nixUnstable
You also need to enable the experimental features in your ~/.config/nix/nix.conf
file by adding:
experimental-features = nix-command flakes
Once you have nix flakes enabled, you can create a new flake by running:
nix flake init
This will generate a flake.nix
file with some basic attributes:
{
description = "A very basic flake";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }: {
# A Nix package
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
# A NixOS module
nixosModules.hello =
{ pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
};
};
}
The description
attribute is a string that describes the flake. The inputs
attribute is an attribute set of all the dependencies of the flake. In this case, we only have one dependency: the nixpkgs flake. The outputs
attribute is a function that takes an attribute set of all the realized inputs and returns another attribute set with the artifacts produced by the flake.
In this example, we have two outputs: a Nix package named hello
and a NixOS module named hello
. The package is defined using the nixpkgs.legacyPackages
attribute, which provides access to the packages defined in nixpkgs. The module is defined as a function that takes pkgs
and other arguments and returns a NixOS configuration.
To build the package, we can run:
nix build .#hello
This will build the hello package defined in the current flake (denoted by .
) and store it in the Nix store. We can also run it directly by using:
nix run .#hello
This will build and run the hello package without installing it.
To use the module, we can create a configuration.nix
file that imports it:
{ config, pkgs, ... }:
{
imports = [ ./flake.nix ];
# Other configuration options...
}
Then we can build and activate our system configuration by running:
nixos-rebuild switch --flake .
This will use the current flake as the source of our system configuration and apply it to our machine.
How to compose nix flakes?
One of the main advantages of nix flakes is that they allow us to compose different Nix projects in a modular and declarative way. For example, suppose we want to use a flake that provides some useful Nix functions, such as flake-utils
:
{
description = "A flake with some useful Nix functions";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }: {
# A Nix package
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
# A NixOS module
nixosModules.hello =
{ pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
};
# A function that creates a flake from a simple Nixpkgs overlay
overlayFlake = flake-utils.lib.simpleFlake;
};
}
Here we have added a new input for the flake-utils
flake and a new output for the overlayFlake
function. This function takes a simple Nixpkgs overlay and returns a flake that provides packages and apps based on it.
We can use this function to create another flake that uses an overlay to customize some packages:
{
description = "A flake that uses an overlay to customize some packages";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-with-functions.url = "/path/to/flake-with-functions";
outputs = inputs@{ self, nixpkgs, flake-with-functions, ... }:
flake-with-functions.overlayFlake {
# An overlay that adds a suffix to the hello package
overlay = final: prev: {
hello = prev.hello.overrideAttrs (old: {
name = "${old.name}-customized";
});
};
# Other arguments passed to simpleFlake
inherit inputs;
name = "customized-hello-flake";
};
}
Here we have created a new flake that depends on the nixpkgs
flake and the flake-with-functions
flake. We use the overlayFlake
function from the latter to create a flake that provides a customized version of the hello package. We pass an overlay that adds a suffix to the package name, as well as other arguments required by the simpleFlake
function.
We can build and run the customized hello package by using:
nix run .#hello
This will show:
Hello, world!-customized
We can also use the niv
tool to manage our flake dependencies in a more convenient way. For example, we can run:
niv init -f
This will create a sources.nix
file that contains the URLs and revisions of our flake inputs. We can then use this file in our flake.nix
file by importing it:
{
description = "A flake that uses an overlay to customize some packages";
inputs = import ./sources.nix;
outputs = inputs@{ self, nixpkgs, flake-with-functions, ... }:
flake-with-functions.overlayFlake {
# An overlay that adds a suffix to the hello package
overlay = final: prev: {
hello = prev.hello.overrideAttrs (old: {
name = "${old.name}-customized";
});
};
# Other arguments passed to simpleFlake
inherit inputs;
name = "customized-hello-flake";
};
}
We can also use niv
to update our dependencies by running:
niv update
This will fetch the latest revisions of our inputs and update the sources.nix
file accordingly.
Conclusion
Nix flakes are a new experimental feature that improve the reproducibility, composability, and usability of Nix projects. They provide a standardized interface to Nix artifacts, a way to specify and pin dependencies, and a hermetic evaluation model. They also enable new ways to deliver software to users, such as using nix profile
or nix bundle
. Flakes are still under development and may change in the future, but they are already usable and provide many benefits over the traditional Nix approach.
If you want to learn more about nix flakes, you can check out the following resources:
References
- Flakes - NixOS Wiki. https://nixos.wiki/wiki/Flakes.
- Nix Flakes, Part 1: An introduction and tutorial - Tweag. https://www.tweag.io/blog/2020-05-25-flakes/.
- Practical Nix Flakes - Serokell Software Development Company. https://serokell.io/blog/practical-nix-flakes.
- A Tour of Nix Flakes | Mattia Gheda. https://ghedam.at/a-tour-of-nix-flakes.