Skip to content

albertodvp/nix-from-zero-to-something

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nix: from zero to something


Table of Contents

  1. Disclaimer
  2. Use Cases (to grab your attention)
  3. Nix Language
  4. Derivations
  5. The nix store
  6. Flakes (short intro)
  7. Killer Use Cases

Disclaimer

  • This demo is "from first principles", it might sound slow
  • I'm not an expert
  • If anything is not clear, please interrupt me
  • I'm not sure this approach/TOC works
  • This is from a fork: https://github.com/aciceri/nixos-devops-talk

Some use cases

Nix as a Language

  • declarative: There is no notion of executing sequential steps. Dependencies between operations are established only through data.
  • pure: Values cannot change during computation. Functions always produce the same output if their input does not change.
  • functional: Functions are like any other value. Functions can be assigned to names, taken as arguments, or returned by functions.

  • lazy: Values are only computed when they are needed.
  • dynamically typed: Type errors are only detected when expressions are evaluated.

Before we dive into derivations, let's cover some key language features that are essential for understanding Nix expressions.


Attribute Sets

Attribute sets are like dictionaries or key-value pairs. They're defined using curly braces:

{ 
  name = "example";
  value = 42;
}

Let Expressions

let bindings allow you to create local variables:

let 
  x = 1;
  y = 2;
in
  x + y  # Returns 3

With Expressions

with brings an attribute set's attributes into scope:

let set = { a = 1; b = 2; };
in with set; a + b  # Returns 3

Function Application

Functions are called by putting the argument after the function, separated by a space:

isNull null  # Returns true

Function Definition

Functions are defined using a colon. The syntax is argument: body

x: x + 1  # A function that takes x and returns x + 1

Functions can also take attribute sets as arguments

{ a, b }: a + b  # A function that takes an attrset with 'a' and 'b' keys

Function Arguments with Defaults

In Nix, function arguments can have default values. If an argument is not provided during the function call, the default value is used.

{ x ? 10 }: x + 5  # A function with a default value for x

Calling this function with no arguments will return 15 because x defaults to 10.

Function Application (user defined function)

(x: x + 1) 5  # Returns 6

For functions taking attribute sets:

({ a, b }: a + b) { a = 1; b = 2; }  # Returns 3

Import

The import function is used to load Nix expressions from files or other sources. It can be used to import files directly or to pull in the Nix Packages collection (nixpkgs).

let
  myModule = import ./path/to/module.nix;
in
  myModule.someAttribute

Importing nixpkgs

The expression <nixpkgs> is a shorthand to refer to the Nix Packages collection (it's a path, depends on how you installed nix)

let
  pkgs = import <nixpkgs> {};
in
  pkgs.hello  # Refers to the 'hello' package from nixpkgs

If nixpkgs "points to master", it's literally this

Using Import with Default Arguments

{ pkgs ? import <nixpkgs> {} }: 
pkgs.hello

Useful to override the pkgs (e.g. pin to a specific version, different from the default one)


Ok, boring... now what?


We use the nix language mostly to produce derivations

Derivations

The core building block that describes how to build a software component. It's a low-level, immutable representation of a build process, which tells Nix exactly what to do to produce a specific output.

TL;DR: a derivation is a build plan.

A very simple derivation

# simple.nix
{ pkgs ? import <nixpkgs> { } }:
pkgs.stdenv.mkDerivation {
  name = "simple";
  src = ./.;
  installPhase = ''
    mkdir $out
    echo "42" > $out/output
  '';
}

Store derivation

A derivation can be instantiated, it became a store derivation

To build this derivation, you would use the command:

nix-instantiate simple.nix

...
this derivation will be built:
  /nix/store/{SOME-HASH}-simple.drv
...


We can navigate inspect a derivation:

nix derivation show /nix/store/{SOME-HASH}-simple.drv

The .drv File (1/2)

A .drv file contains:

  • Build Instructions: How to fetch, unpack, build, and install the package.
  • Dependencies: The dependencies required for the build, including other packages and build tools.

The .drv File (2/2)

  • Source Information: Where to find the source code or files needed for the build.
  • Phases: Various build phases like unpackPhase, patchPhase, buildPhase, installPhase, etc;
  • Output Paths: Paths where the build outputs are placed in the Nix store.
  • ... other "less interesting" things.

Store Derivation Path

/nix/store/<hash>-<name>
  • The hash in the path is generated from the various inputs that make up the derivation. If all inputs remain unchanged, the resulting derivation will always produce the same hash and store path.
  • This approach is highly powerful, as it enables the use of caches and substitutes, allowing pre-built derivations to be shared and reused, significantly improving build efficiency.

Derivation realisation

nix-store --realise /nix/store/{SOME-HASH}-simple.drv

Shortcut & Summary: nix-build

nix-build does:

  • Instantiation: The .nix file is instantiated into a store derivation (.drv).
  • Realization: The .drv is then realized into the actual build output, based on its defined outputs (e.g., outputs.out.path).
  • A symbolic link ./result is created, pointing to the path of the realized output.

Note

  • nix-build vs nix build:
    • nix-build is the older command used to build Nix expressions directly from files.
    • nix build is a newer command that is more compatible with flakes and newer Nix features. It's recommended to use nix build if you're working with flakes or modern Nix expressions, as it integrates better with the new ecosystem.

Let's Play with Our Derivation


Here's an example of a simple derivation that compiles a C program:

# hello.nix
{ pkgs ? import <nixpkgs> { } }:
pkgs.stdenv.mkDerivation {
  name = "hello";
  src = ./src;

  buildPhase = ''
    rustc $src/rust.rs -o ./hello
  '';
  installPhase = ''
    mkdir -p $out/bin
    cp ./hello $out/bin
  '';
}

Something is missing here...


What are $src and $out?

  • $src: This variable refers to the source code directory specified by the src attribute. In this example, it's the ./src directory.
  • $out: This variable represents the output directory in the Nix store where the build results will be placed. Nix automatically sets this variable to a unique path in the /nix/store.

If the derivation depends on other derivations, these are built first.


Building the Derivation (from nixpkgs)

  • Nixpkgs is a vast repository that contains derivations for most of the software packages you might need.
  • It's the primary package collection used by Nix and NixOS, offering a wide variety of software from simple utilities to complex applications.

For example, the sl package in nixpkgs can be examined and built with the following commands:

nix derivation show nixpkgs#sl
nix build nixpkgs#sl -L --rebuild

Nix Store

Nix Store

Nix Store

Its primary purpose is to store both the derivation files (.drv) and their output artifacts.

  • Location: The Nix store is located at /nix/store.
  • Immutability: Once a derivation is built and stored in the Nix store, it never changes.
  • Accessibility: The store is readable by all users, allowing for shared access.

Caches

Before Nix realise a derivation, it checks the cache to see if the output already exists. If it does, Nix can download the output directly from the cache instead of rebuilding it.


NixOS

What if the entire operating system was the output of a derivation? NixOS is a basically a big configuration file (.nix) that evaluates to a a big derivation that build a Linux disto (plus some symbolic links that enables easy rollbacks).


Problems

  • Everyone writes nix expressions as they want
  • nixpkgs (inputs in general) are not pinned

What Are Flakes?

What Are Flakes?

  • introduce more structured and reliable way to manage Nix projects
  • are an experimental feature
  • are expected to become the standard in future versions of Nix
 --experimental-features 'nix-command flakes'

{ self, ... }@inputs:
{
  # Executed by `nix flake check`
  checks."<system>"."<name>" = derivation;

  # Executed by `nix build .#<name>`
  packages."<system>"."<name>" = derivation;

  # Executed by `nix build .`
  packages."<system>".default = derivation;

  # Executed by `nix run .#<name>`
  apps."<system>"."<name>" = {
    type = "app";
    program = "<store-path>";
  };

  # Executed by `nix run . -- <args?>`
  apps."<system>".default = { type = "app"; program = "..."; };

  # Formatter (alejandra, nixfmt or nixpkgs-fmt)
  formatter."<system>" = derivation;

  # Used by `nix develop .#<name>`
  devShells."<system>"."<name>" = derivation;

  # Used by `nix develop`
  devShells."<system>".default = derivation;
}

Killer Use Uses

Dev Shells

  • Easier Onboarding: New developers quickly get up and running with a nix-shell that configures all tools and dependencies automatically, eliminating setup hassles.
  • Consistent Environment: Ensures all team members use the same development environment, preventing discrepancies and conflicts.

Reproducible Builds

  • Consistent Builds: Guarantees that builds are identical across different machines, eliminating “it works on my machine” issues.
  • Stable CI Pipelines: CI systems benefit from reproducible builds as they ensure that builds are consistent and reliable, leading to more accurate testing and integration results.
  • Efficient Caching: Reproducible builds enable the use of caching mechanisms to speed up development.

From here

Some ideas of what we could do:

  • let's install nix on your machine (nix is supported for macOS and WSL2 too)
  • let's nixify your project (build your code, setup a developer environment, ...)
  • let's convince your company that nix will make it save money (onboarding, reproducibility, ...)
  • try conving me nix is not the solution for everything

Releases

No releases published

Packages

No packages published

Languages

  • Nix 74.2%
  • HTML 13.6%
  • CSS 9.6%
  • Shell 1.3%
  • Rust 1.3%