In Devbox 0.4.7, we added support for installing packages from Nix flakes. For power users of Nix, this provides more flexibility and customization for your Devbox project. Using the power of flakes, developers can now create their own packages, modify nixpkgs, or install packages from sources outside of the Nix store.
In this post, we'll demonstrate how to use a Nix flake to modify a package from the Nixpkg repository and then use that modified package in our Devbox project. We'll be using the overlay example from our Devbox repo.
What is a Flake?
A flake is a simplified way to package software with Nix that lets you declaratively define dependencies and build instructions. Unlike most Nix files or derivations, flakes have a strict schema for representing your packages and outputs.
The high-level schema looks something like this:
{
description = "This flake outputs a modified version of Yarn that uses NodeJS 16"
inputs = {} # A set of the dependencies our flake will use
outputs = {}: {} # A function that turns our dependencies into packages, apps, and other outputs.
}
Let's go through each part in detail, to show how we will build our modified yarn
package.
Inputs
inputs = {
nixpkgs.url = "nixpkgs/nixos-21.11";
flake-utils.url = "github:numtide/flake-utils";
};
Our flake is going to use two inputs to create our packages. We'll define our input by mapping a name to a flake reference that tells Nix where to fetch it:
Nixpkgs is the default repository of Packages for Nix. We define an input name, nixpkgs, and set its URL to "nixpkgs/nixos-21.11"
Flake-utils is a set of functions that make developing Nix Flakes easier. We define a name flake-utils and set its reference to a Github-hosted flake using "github:numtide/flake-utils"
Whenever we build the outputs for our Flake, Nix will pull these inputs from their URL and pin them in a flake.lock
file. This file ensures that we will use the same sources every time we build or run the Flake.
With these inputs defined, we can use them in our outputs closure to create the packages we want.
Further Reading
Nix Manual entry on Flake Inputs
Outputs:
A flake's output is a function that takes a set of inputs, and returns an attribute set of packages, apps, templates, and other outputs that users can reference.
In this case, the input set consists of the following:
{ self, # A reference to the Flake itself
nixpkgs, #Our nixpkgs input defined in the previous section
flake-utils, #The flake-utils reference defined in the section above
}
We will now use these inputs to generate an attribute set of outputs.
eachDefaultSystem
Normally flakes require us to specify an output for each system we want to support (e.g., aarch64-darwin
or x86_64-linux
), but this can be tedious to write. To simplify things, we're going to use the eachDefaultSystem function from flake-utils to generate outputs for the default set of systems:
outputs = {self, nixpkgs, flake utils}:
flake-utils.lib.eachDefaultSystem (system:
# Define out Outuputs here
);
Now we can focus on the outputs we want to generate. We’ll define a few variables that we want to use in our outputs using a let expression:
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
# Define an overlay function
overlay = (final: prev: {
yarn = prev.yarn.override {
nodejs = final.pkgs.nodejs-16_x;
};
});
# Import Nixpkgs with our overlay applied
pkgs = import nixpkgs {
inherit system;
overlays = [ overlay ];
};
in {...}
);
Let’s take each of these definitions in more detail.
Overlay
An overlay is a Nix function that lets us modify a set of packages. You can use overlays to override values in one or more packages within a set like nixpkgs
.
An overlay takes the following arguments and returns a modified package set:
final: which corresponds to the final package set that we are going to return
previous: which corresponds to the previous package set that we are modifying
In the function's closure, we modify packages using override to change the package's value. In this example, we override yarn to use a different version of Node.js:
overlay = (final: prev: {
yarn = prev.yarn.override {
nodejs = final.pkgs.nodejs-16_x;
};
});
Further Reading
Overlays (Nix Manual)
Examples of Overlays (Nix Wiki)
Import
This section evaluates nixpkgs and returns a set of packages based on our current system and overlay. We tell nixpkgs to inherit the system from its current scope and apply the overlay from the previous section. We assign the result of this evaluation to the pkgs variable, which we will use for our final output.
pkgs = import nixpkgs {
inherit system;
overlays = [ overlay ];
};
Further Reading
Our Output Package
Now that we have a modified nixpkgs
set, we can output the modified package for our project. We'll use the packages
attribute in our Flake to tell Nix that we want to return a package. The code for this is pretty simple:
in {
packages = {
yarn = pkgs.yarn;
};
}
This section means that when someone builds the yarn output of the flake, we will return the yarn package from the pkgs set we defined above.
Further Reading
Putting all the Pieces Together
Our complete flake.nix file should look like this:
{
description =
"This flake outputs a modified version of Yarn that uses NodeJS 16";
inputs = {
nixpkgs.url = "nixpkgs/nixos-21.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
overlay = (final: prev: {
yarn = prev.yarn.override { nodejs = final.pkgs.nodejs-16_x; };
});
#
pkgs = import nixpkgs {
inherit system;
overlays = [ overlay ];
};
in {
packages = { yarn = pkgs.yarn; };
});
}
If we want to test the flake, we can run nix build .#yarn
. This will generate the yarn output and link the results in our local directory.
Using our Flake in Devbox
To use this yarn package in our devbox.json
project, we provide a flake reference that points to our yarn
output of our flake:
{
"packages": [
"path:yarn-overlay#yarn"
]
}
Now when we start Devbox, our yarn
package will use Node.js 16!
In this example we showed how you can use flakes to modify the default settings of a package from nixpkgs. For more ideas on how to use Nix Flakes, you can check out the flakes examples in our Devbox Repo.
Stay up to Date with Jetify
If you're reading this, we'd love to hear from you about how you've been using Devbox for your projects. You can follow us on Twitter, or chat with our developers live on our Discord Server. We also welcome issues and pull requests on our Github Repo.