Skip to Content
Software EngineeringNixnix flake - manage Nix flakes

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 to package.json (JavaScript) or Cargo.toml (Rust).
    • flake.lock: Locks specific dependency versions for reproducibility, similar to package-lock.json or Cargo.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 flakes

Flake 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:

  1. description: A string that provides a description of the flake.

  2. inputs: An attribute set defining all the dependencies of the flake. This includes external flakes and Nix packages needed for the flake.

  3. 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.

  4. nixConfig: An attribute set of values reflecting the configuration given to nix.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 uses flake-utils to handle multiple systems and creates a package using stdenv.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 inputs section in flake.nix.
  • nix-env: Manages user environment packages, influenced by nix-channel.
    • New CLI: Replaced by nix profile, but not recommended for beginners.
  • nix-shell: Creates a temporary shell environment for development/testing.
    • New CLI: Replaced by nix develop, nix shell, and nix run.
  • nix-build: Builds Nix packages, placing results in /nix/store.
    • New CLI: Replaced by nix build.
  • nix-collect-garbage: Cleans unused objects in /nix/store.
    • New CLI: Partially replaced by nix store gc --debug (no alternative for profile generation cleanup).

Templates for Development Environments

  • Simplifying Flake Creation: Writing flake.nix for each project can be tedious. The community has created templates to ease this process:

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 --user fails, as Nix disables global modification commands to improve reproducibility.

  • Solutions:

    1. 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.

    2. Installing Dependencies via Nix:

      • Install Python dependencies (e.g., from requirements.txt or poetry.toml) via Nix for immutability and reproducibility:
        • Tools like poetry2nix or other Nix packaging tools can help.
        • These tools rely on the lock mechanism of Nix Flakes for improved reproducibility.
    3. 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

  1. 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
  2. Create a Go Project:

    mkdir ~/tmp/go-demo cd ~/tmp/go-demo nix flake new -t templates#go-hello . git init && git add .
  3. Generated Files:

    • flake.lock, flake.nix, go.mod, main.go
  4. Building and Running:

    • Build: nix build
    • Run: ./result/bin/go-hello

Understanding Flake Outputs

  • Default Package:

    • Defined in packages section of flake.nix.
    • Build it with: nix build .#default
  • Expose as Application:

    • Add apps section in flake.nix to run with nix run:

      apps = forAllSystems (system: { default = { type = "app"; program = "${self.packages.${system}.default}/bin/go-hello"; }; });

Configuring Development Environments

  • Declare Development Tools in flake.nix using devShells:

    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 nixpkgs or other flakes:

    inputs.nixpkgs.url = "nixpkgs/nixos-21.11";
  • Updating Dependencies:

    • Run nix flake update to update dependencies.
  • Shared Dependencies: Use follows to avoid version conflicts:

    inputs.xess.inputs.nixpkgs.follows = "nixpkgs";

Backwards Compatibility

  • For Non-Flake Projects: Use flake-compat to 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)"
Last updated on