nix flake - manage Nix flakes
Overview of Nix Flakes
- Flakes: A new experimental Nix feature designed to improve reproducibility, composability, and usability by managing dependencies between Nix expressions.
- Flake Structure:
flake.nix: Defines the project’s dependencies, metadata, and outputs (e.g., packages, NixOS modules), similar topackage.json(JavaScript) orCargo.toml(Rust).flake.lock: Locks specific dependency versions for reproducibility, similar topackage-lock.jsonorCargo.lock.
- Adoption: Although still experimental, flakes are widely used within the Nix community due to their significant enhancements to the ecosystem.
Problems Flakes Solve
- Reproducibility issues: Nix evaluation isn’t fully reproducible; flakes ensure strict hermetic evaluation.
- Dependency management: Flakes declare explicit dependencies and use lock files to pin them to specific revisions.
- Standardized project structure: Flakes provide a common structure and are more composable compared to channels or manually updated Git hashes.
Why Nix Flakes Matter
- Flakes: Introduces conventions for building, running, and deploying software in Nix, addressing Nix’s lack of out-of-the-box conventions.
- Comparison: Nix Flakes are to Nix what docker-compose is to Dockerfiles—they help manage, integrate, and compose packages across machines.
- Benefits of Flakes:
- Adds project templates to Nix for easy project setup.
- Defines a standard way to expose runnable programs.
- Consolidates development environments into project configurations.
- Trivially pulls in external dependencies from Git repos.
- Supports private Git repositories.
- Embeds system configuration alongside application code.
- Embeds the Git hash of configuration repositories into deployed machines.
Enabling Flakes in Nix (without using nixOS nor home manager)
Add the following line to ~/.config/nix/nix.conf to enable experimental features:
experimental-features = nix-command flakesFlake Schema Overview
Here’s a summary of the Flake schema and an example:
Flake Schema
The flake.nix file has a special format with four main attributes:
-
description: A string that provides a description of the flake. -
inputs: An attribute set defining all the dependencies of the flake. This includes external flakes and Nix packages needed for the flake. -
outputs: A function that takes an attribute set of all the resolved inputs and returns another attribute set. This attribute set describes what the flake produces, such as packages, configurations, or other outputs. -
nixConfig: An attribute set of values reflecting the configuration given tonix.conf. This allows for flake-specific configurations, such as binary caches, extending the behavior of the user’s Nix experience.
Example flake.nix
{
description = "From OpenTechLab YT: C++ Application Example";
inputs = {
nixpkgs.url = "nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs { inherit system; };
in {
packages = {
default = pkgs.stdenv.mkDerivation {
name = "nix_test_app";
src = ./.;
nativeBuildInputs = with pkgs; [ cmake ];
buildInputs = with pkgs; [ boost185 ]; # Boost also works
};
};
}
);
}Explanation
description: Provides a brief description of the flake’s purpose.inputs: Specifies dependencies, including Nixpkgs and other flakes.outputs: Defines what the flake produces. In this case, it usesflake-utilsto handle multiple systems and creates a package usingstdenv.mkDerivation.nixConfig: Not used in this example but would include additional configuration if needed.
New CLI vs Classic CLI
- Nix Experimental Features (2020): Introduced nix-command (New CLI) and flakes, significantly improving the Nix ecosystem.
- Tight Coupling: Flakes require the use of the New CLI, although there are ongoing efforts to separate them.
Replacing Classic CLI Commands with the New CLI
nix-channel: Manages software versions via channels (stable/unstable/test).- New CLI: Replaced by the
inputssection inflake.nix.
- New CLI: Replaced by the
nix-env: Manages user environment packages, influenced bynix-channel.- New CLI: Replaced by
nix profile, but not recommended for beginners.
- New CLI: Replaced by
nix-shell: Creates a temporary shell environment for development/testing.- New CLI: Replaced by
nix develop,nix shell, andnix run.
- New CLI: Replaced by
nix-build: Builds Nix packages, placing results in/nix/store.- New CLI: Replaced by
nix build.
- New CLI: Replaced by
nix-collect-garbage: Cleans unused objects in/nix/store.- New CLI: Partially replaced by
nix store gc --debug(no alternative for profile generation cleanup).
- New CLI: Partially replaced by
Templates for Development Environments
- Simplifying Flake Creation: Writing
flake.nixfor each project can be tedious. The community has created templates to ease this process:- Repositories:
- Simpler Nix Encapsulation: For users who find
flake.nixtoo complex, projects like cachix/devenv offer more abstraction. - No Nix Code: For those wanting minimal effort, jetpack-io/devbox provides an easy way to get reproducible environments without writing any Nix code.
Python Development Environment in NixOS
-
Challenges:
-
Python defaults to installing globally, which NixOS disallows, leading to errors like:
error: externally-managed-environment -
Even using
pip install --userfails, as Nix disables global modification commands to improve reproducibility.
-
-
Solutions:
-
Using Virtual Environments:
-
Create a virtual environment for project isolation:
python -m venv ./env source ./env/bin/activate -
Alternatively, use virtualenv, but this requires additional installation.
-
-
Installing Dependencies via Nix:
- Install Python dependencies (e.g., from
requirements.txtorpoetry.toml) via Nix for immutability and reproducibility:- Tools like
poetry2nixor other Nix packaging tools can help. - These tools rely on the lock mechanism of Nix Flakes for improved reproducibility.
- Tools like
- Install Python dependencies (e.g., from
-
Containers:
- For complex projects where the above solutions are insufficient, use containers like Docker or Podman for greater compatibility and fewer restrictions compared to Nix.
-
Setting Up a Go Project with Nix Flakes
-
Enable Flakes:
-
NixOS: Add to
configuration.nix:nix = { package = pkgs.nixFlakes; extraOptions = '' experimental-features = nix-command flakes ''; }; -
Non-NixOS: Add to
~/.config/nix/nix.conf:experimental-features = nix-command flakes
-
-
Create a Go Project:
mkdir ~/tmp/go-demo cd ~/tmp/go-demo nix flake new -t templates#go-hello . git init && git add . -
Generated Files:
flake.lock,flake.nix,go.mod,main.go
-
Building and Running:
- Build:
nix build - Run:
./result/bin/go-hello
- Build:
Understanding Flake Outputs
-
Default Package:
- Defined in
packagessection offlake.nix. - Build it with:
nix build .#default
- Defined in
-
Expose as Application:
-
Add
appssection inflake.nixto run withnix run:apps = forAllSystems (system: { default = { type = "app"; program = "${self.packages.${system}.default}/bin/go-hello"; }; });
-
Configuring Development Environments
-
Declare Development Tools in
flake.nixusingdevShells:devShells = forAllSystems (system: let pkgs = nixpkgsFor.${system}; in { default = pkgs.mkShell { buildInputs = with pkgs; [ go gopls gotools go-tools ]; }; }); -
Enter the development shell with
nix develop.
Handling External Dependencies
-
Add external dependencies like
nixpkgsor other flakes:inputs.nixpkgs.url = "nixpkgs/nixos-21.11"; -
Updating Dependencies:
- Run
nix flake updateto update dependencies.
- Run
-
Shared Dependencies: Use
followsto avoid version conflicts:inputs.xess.inputs.nixpkgs.follows = "nixpkgs";
Backwards Compatibility
-
For Non-Flake Projects: Use
flake-compatto maintain compatibility:(import (fetchTarball { url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz"; sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }) { src = ./.; }).defaultNix
Embedding NixOS Modules in Flakes
-
System Configuration can be defined in the flake alongside the software:
nixosModules.bot = { config, lib, ... }: { systemd.services.mara-bot = { ExecStart = "${self.packages."${system}".default}/bin/mara"; }; };
Embedding Configuration Git Hash
-
Track System Configuration with
nixos-version --json:echo "https://tulpa.dev/cadey/nixos-configs/src/commit/$(ssh logos nixos-version --json | jq -r .configurationRevision)"