Nix Flakes Explained

2025-05-05

Nix Flakes For NixOS Configuration Explained

let
  my_attrset = { foo = "bar"; };
in my_attrset.foo

Output: "bar"

Basic Flake Structure:

{
  description = package description
  inputs = dependencies
  outputs = what the flake produces
  nixConfig = advanced configuration options
}

Inputs

The following specifies a dependency on the nixpkgs and import-cargo repositories:

inputs = {
  import-cargo.url = "github:edolstra/import-cargo";
  nixpkgs.url = "nixpkgs";
}

Outputs

Show your flakes outputs with:

nix flake show

This command actually takes a flake URI and prints all the outputs of the flake as a nice tree structure, mapping attribute paths to the types of values.

[!NOTE]: The ... syntax is for variadic attributes, (i.e. a varying number of attributes). If you notice most flakes have many more inputs than are explicitly listed in the input arguments this is possible because of variadic attributes.

In the following example c = 2 is an extra attribute:

mul = { a, b, ... }: a*b
mul { a = 3; b = 4; c = 2; }

However, in the function body you cannot access the "c" attribute. The solution is to give a name to the given set with the @-pattern:

nix-repl> mul = s@{ a, b, ... }: a*b*s.c  # s.c = 2
nix-repl> mul { a = 3; b = 4; c = 2; }
24
{
  inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
  inputs.home-manager.url = github:nix-community/home-manager;

  # outputs is a function that takes an attribute set that returns an
  # attribute set (e.g. outputs multiple values)
  outputs = { self, nixpkgs, ... }@attrs: {

    # a `packages` output
    packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;

    # Below is the nixosConfigurations output (e.g. your NixOs configuration)
    nixosConfigurations.fnord = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      specialArgs = attrs;
      modules = [ ./configuration.nix ];
    };
  };
}

Imports

{
  outputs = { self, nixpkgs, ... }: {
    nixosConfigurations.my-system = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./modules/base.nix
        (import ./modules/desktop.nix { pkgs = nixpkgs; })
      ];
    };
  };
}

When you see:

let
  myHelpers = import ./lib/my-helpers.nix { pkgs = nixpkgs; };
in

You are:

  1. Importing the Nix expression from ./lib/my-helpers.nix

  2. Passing an attribute set { pkgs = nixpkgs; } as an argument to the evaluated expression in the imported file.

Inside lib/my-helpers.nix, there will likely be a function definiton that expects an argument (often also named pkgs by convention):

# ./lib/my-helpers.nix
{ pkgs }:
let
  myPackage = pkgs.stdenv.mkDerivation {
    name = "my-package";
    # ...
  };
in
myPackage

Resources