Repository with examples around Nix package manager and NixOS.
I use it to present this wonderful piece of technology to other people.
Here is a QR code with the address of this repository https://github.com/corpix/nix-showcase
███████████████████████████████ █ ▄▄▄▄▄ ██▄▄ ▀ ██▀▄▀███ ▄▄▄▄▄ █ █ █ █ █▀▄ █▀▄▄▀▀▄▄██ █ █ █ █ █▄▄▄█ █▄▀ █▄█▀█ ▄ ▄ █ █▄▄▄█ █ █▄▄▄▄▄▄▄█▄▀▄█ █ ▀▄█▄▀ █▄▄▄▄▄▄▄█ █▄ ▀▀█▄▄▄▀▄ █▄▀▀ █ █ █▀█▀ ▄▀▀██ █▄ ▀ ▄▀█▄▀ ▄▄█ █ ▀▄▀██▄▀████ █▄██ ▀█▄ ▄ █▀ ▄ ▀▀ █ ▀██▀ █ █▀ █▄█ ▄▄▄▄██▀ ▄▄█▀▄▄█▀ ▄ █▀█ █▀▄█ ▀ ▄█▀▄▄█▄█▄▀▀ ▀ █▄▀ ▀▄ ▀▀█ █ ▄▀█▀ ▄ ▀▄▄▀▄▄ ▄▄█▀▄▀█▀█▄▄██▄█ █▄█▄▄▄█▄▄▀█▀▄▀▀█▀█ ▀▄ ▄▄▄ ▀▄▄▄█ █ ▄▄▄▄▄ █ ███▀ ▄ ▄█▄ █▄█ ▄▄▀▀█ █ █ █ ██▄▀▄▄▄█ ▀▀█ ▄▄▄ ▀█ █ █ █▄▄▄█ █▀ ▀ ▄██▄▄█ ▄▄▄ ▄ ▄▀█ █▄▄▄▄▄▄▄█▄▄▄▄█████▄▄▄██▄▄▄█▄███
For all commands inside this repository you will need a Nix package manager or NixOS in some cases (this requirement will be mentioned explicitly).
I recommend to use Nix package manager inside docker container:
$ make run/docker/shell
Also I should mention there is an official guide which will help you to install Nix package manager to you system. But if you don’t use Linux and you don’t feel yourself fluent with Nix then i recommend you to use dockerized environment from this repository.
Useful links:
- https://nixos.org/manual/nix/stable/#ch-expression-language
- https://nixos.wiki/wiki/Nix_Expression_Language
- https://ianthehenry.com/posts/how-to-learn-nix/introduction/
$ nix repl Welcome to Nix version 2.3.15. Type :? for help. nix-repl> :? The following commands are available: <expr> Evaluate and print expression <x> = <expr> Bind expression to variable :a <expr> Add attributes from resulting set to scope :b <expr> Build derivation :i <expr> Build derivation, then install result into current profile :l <path> Load Nix expression and add it to scope :p <expr> Evaluate and print expression recursively :q Exit nix-repl :r Reload all files :s <expr> Build dependencies of derivation, then start nix-shell :t <expr> Describe result of evaluation :u <expr> Build derivation, then start nix-shell
- variables
- functions
- booleans
- null
- numbers
- integers
- floats
- strings
- regular expressions
- paths
- uri
- attribute sets
- lists
- control flow
- errors
- laziness
- derivations
Lexical scoping:
nix-repl> let a = "hello"; b = "world"; in a + b "helloworld"
All variables in let
are declarative, so order is not enforced:
nix-repl> let a = b; b = "thing"; in a + b "thingthing" nix-repl> x = 1 nix-repl> let y = x; x = 2; in y 2
let add = a: b: a + b; in add 1 2
This will output 3
.
Function like a: b: ...
has two positional arguments, but:
- you could define one or more positional arguments, like
a: b: c: d: ...
- you could partially apply function, like
((add 1) 2)
is the same asadd 1 2
So, each function could be partially applied:
nix-repl> fn = a: b: c: a + b + c nix-repl> fn 1 «lambda @ (string):1:5» nix-repl> fn 1 2 «lambda @ (string):1:8» nix-repl> fn 1 2 3 6
In addition basic destructuring is supported:
nix-repl> fn = { a, b, c }: a + b + c nix-repl> fn { a = 1; b = 2; c = 3; } 6
While using destructuring a default values could be specified:
nix-repl> fn = { a, b ? 2, c ? 3 }: a + b + c nix-repl> fn { a = 1; } 6 nix-repl> fn { a = 1; b = 1; } 5
Destructuring results could be accessed as an attribute set:
nix-repl> fn = { a, b, c } @ attrs: attrs nix-repl> fn { a = 1; b = 2; c = 3; } { a = 1; b = 2; c = 3; } nix-repl> fn = { a, b, c ? 3 } @ attrs: attrs nix-repl> fn { a = 1; b = 2; } { a = 1; b = 2; } nix-repl> fn { a = 1; b = 2; c = 3; } { a = 1; b = 2; c = 3; } nix-repl> fn = { a, b, c, ... } @ attrs: attrs nix-repl> fn { a = 1; b = 2; c = 3; d = 4; } { a = 1; b = 2; c = 3; d = 4; }
For boolean true/false there are separate keywords:
true
false
!
is booleannot
,!true
isfalse
&&
is booleanand
,true && false
isfalse
||
is booleanor
,false || true
istrue
->
is booleanimpl
,true -> true
istrue
, equivalent of!true || true
All this expressions must evaluate to boolean, thus true && 1
is not valid.
- ==== equal,
1==1
istrue
,1==2
isfalse
!=
not equal,2!=2
istrue
You could check for equality between values of different type:
- there is no error
- but there is no implicit type conversion
- so different types are always not equal
Here is an examples for types we have not talked about, but they are worth metioning:
nix-repl> {} == {} true nix-repl> { a = 1; } == { a = 1; } true nix-repl> { a = { b = 1; }; } == { a = { b = 1; }; } true nix-repl> [ 1 2 3 ] == [ 1 2 3 ] true nix-repl> [ 1 2 3 ] == [ 1 2 ] false
Null is just null
, like in other languages.
nix-repl> fn = { var ? null }: if var == null then "user has not provide us a `var` value" else var nix-repl> fn {} "user has not provide us with `var` value" nix-repl> fn { var = 1; } 1
There are numeric operators:
All are left associative
+
addition,2 + 2
is4
-
subtraction,4 - 2
is2
*
multiplication,3 * 3
is9
/
division,9 / 3
is3
There is a caveat with division operator, you should always add space in between. Otherwise it will be interpreted as a different type -
path
, we will talk about this bellow
You could mix integers with floats, this is valid. But in this case result is always float.
In case you need an integer division use builtins.div
, for example:
nix-repl> builtins.div 4 2 2 nix-repl> builtins.div 7 4 1
You could find functional analogs for other operators under builtins
.
Strings support interpolation.
nix-repl> "hello" "hello" nix-repl> who = "world" nix-repl> "hello ${who}" "hello world"
Where is no implicit type conversion, so every expression under interpolation should be string:
nix-repl> who = 1 nix-repl> "hello ${who}" error: cannot coerce an integer to a string, at (string):1:2 nix-repl> "hello ${builtins.toString who}" "hello 1"
Strings could be multiline:
'' hello world ''
There is no separate type for regular expressions, they are represented with usual strings and applied with builtins.match
:
match
is using extended POSIX regular expressions
nix-repl> builtins.match "http" "http://ya.ru/hello" null nix-repl> builtins.match ".+" "http://ya.ru/hello" [ ] nix-repl> builtins.match "(.+)" "http://ya.ru/hello" [ "http://ya.ru/hello" ] nix-repl> builtins.match "http://([^/]+).*" "http://ya.ru/hello" [ "ya.ru" ]
Path is a filesystem object representing files and directories which is a separate type:
nix-repl> builtins.isPath /foo true nix-repl> builtins.isPath "/foo" false nix-repl> builtins.typeOf /foo "path" nix-repl> /foo + /bar /foo/bar nix-repl> /foo + /bar + "/baz" /foo/bar/baz
nix-repl> builtins.readDir ./. { ".cache" = "directory"; ".config" = "directory"; ".fish.conf" = "regular"; ".git" = "directory"; ".gitignore" = "regular"; ".local" = "directory"; ".personal.fish.conf" = "regular"; ".personal.tmux.conf" = "regular"; ".tmux" = "unknown"; ".tmux.conf" = "regular"; Makefile = "regular"; "README.org" = "regular"; container = "directory"; dotfiles = "directory"; "nix-cage.json" = "regular"; "nixpkgs.nix" = "regular"; "shell.nix" = "regular"; "tools.nix" = "regular"; } nix-repl> builtins.readDir "./." error: string './.' doesn't represent an absolute path, at (string):1:1
URI’s are also supported which are syntactic sugar, but not a separate type:
nix-repl> http://example.org/foo.tar.bz2 "http://example.org/foo.tar.bz2" nix-repl> http://example.org/foo.tar.bz2?foo=bar "http://example.org/foo.tar.bz2?foo=bar" nix-repl> builtins.typeOf http://example.org/foo.tar.bz2 "string"
This is crucial data type of the whole language.
Attribute sets are like hash-maps, but a bit more advanced.
nix-repl> {} { } nix-repl> { a = 1; } { a = 1; }
Attribute sets have a shortcut to define nested keys:
:p
is a repl helper which prints value expanding expression recursively
nix-repl> { a.b.c = 1; } { a = { ... }; } nix-repl> :p { a.b.c = 1; } { a = { b = { c = 1; }; }; }
Attribute sets could be recursive, to define a recursive attribute set prepend it with rec
keyword:
nix-repl> rec { a = 1; b = a; } { a = 1; b = 1; }
Given this we could say that let
is just an attribute set underneath.
Attribute sets could inherit
keys of each other:
nix-repl> baseSet = { a = 1; b = 2; } nix-repl> { inherit (baseSet) a b; c = 3; } { a = 1; b = 2; c = 3; }
We could also use inherit
inside let
(because it is just an attribute set).
To access individual keys of the attribute set dot notation is used (.
is an operator called select
):
nix-repl> a = { foo = 1; bar = 2; } nix-repl> a.foo 1 nix-repl> a.bar 2 nix-repl> x = { a.b = 1; } nix-repl> x . a . b 1
Attribute set keys could be defined from variable or with a string:
nix-repl> key = "keyName" nix-repl> :p { foo.${key} = 1; } { foo = { keyName = 1; }; } nix-repl> :p { foo."${key}Interpolated" = 1; } { foo = { keyNameInterpolated = 1; }; }
You could merge attribute sets with //
operator, thus deep-merge is not providen by builtins (nixpkgs
library has lib.recursiveUpdate
):
nix-repl> { a = 1; } // { a = 2; b = 3; } { a = 2; b = 3; } nix-repl> { a = 1; } // { a = 2; b = 3; } // { c = 4; } { a = 2; b = 3; c = 4; } nix-repl> :p { a = { b = 1; }; } // { a = { b = 2; }; } { a = { b = 2; }; }
Also you could get values & names of the attribute sets (order is guaranteed):
nix-repl> builtins.attrValues { a = 1; b = 2; } [ 1 2 ] nix-repl> builtins.attrNames { a = 1; b = 2; } [ "a" "b" ]
Attribute sets could be used in combination with with
operator to define lexical scopes from attribute sets:
nix-repl> with { a = 1; b = 2; }; a + b 3
Basic lists:
nix-repl> [1 2 3] [ 1 2 3 ] nix-repl> [1 2 (3 + 1)] [ 1 2 4 ]
List concatenation:
nix-repl> [1 2] ++ [3 4] [ 1 2 3 4 ]
List elements:
nix-repl> builtins.head [1 2 3] 1 nix-repl> builtins.tail [1 2 3] [ 2 3 ] nix-repl> builtins.elem 1 [0 1 2] true nix-repl> builtins.elem 1 [0 2] false nix-repl> builtins.elemAt [0 1] 1 1
nix-repl> map (item: item + 1) [1 2 3] [ 2 3 4 ]
Defining fold
(reduce
) via recursion (this function is available in nixpkgs
):
nix-repl> fold = op: nul: list: with builtins; let len = length list; loop = n: if n == len then nul else op (elemAt list n) (loop (n + 1)); in loop 0 nix-repl> fold (value: acc: value ++ acc) [] [[1 2][3 4]] [ 1 2 3 4 ]
For attribute sets there is builtins.mapAttrs
:
nix-repl> :p builtins.mapAttrs (name: value: [name value]) { a = 1; b = 2; } { a = [ "a" 1 ]; b = [ "b" 2 ]; }
We have seen if
:
nix-repl> if 1 == 1 then "equal" else "not equal" "equal"
And thats all you have to control the execution :)
Throwing an error breaks the execution:
nix-repl> throw "oops" error: oops
There is a syntactic sugar which allows to check prerequisites in expressions, just prepend expression with assert expr;
:
nix-repl> assert true; "everything is ok" "everything is ok" nix-repl> assert false; "everything is ok" error: assertion false failed at (string):1:1
Simple tracing expression is available:
nix-repl> builtins.trace "value" "expression" trace: value "expression" nix-repl> map (value: builtins.trace value value) [1 2 3 4] trace: 1 trace: 2 trace: 3 trace: 4 [ 1 2 3 4 ]
Every expression is lazy:
nix-repl> x = builtins.trace "i am lazy" "result" nix-repl> x trace: i am lazy "result"
Derivations is a fancy name for term «package».
Well… not quite, but it could be comfortable to think about derivation as a package
We will discuss a low-level derivations which usualy not used directly (nixpkgs
provides high-level tools to build packages).
Here is an example:
We will use some packages from
nixpkgs
here to keep things simple. You should restartnix repl
with<nixpkgs>
argument, exit the repl and type:nix repl '<nixpkgs>'
nix-repl> :b derivation { name = "foo"; system = "x86_64-linux"; builder = pkgs.writeScript "builder.sh" '' #!${pkgs.bash}/bin/bash -e ${coreutils}/bin/mkdir $out ${coreutils}/bin/touch $out/hello ''; } [2 built, 0.0 MiB DL] this derivation produced the following outputs: out -> /nix/store/ah2zr4q1s8kvzd134qvkk074nmghj307-foo
This provides one output named out
, outputs are atomic parts of the package.
Let’s inspect the filesystem:
$ ls -la /nix/store/ah2zr4q1s8kvzd134qvkk074nmghj307-foo .r--r--r-- 0 nobody 1 Jan 1970 hello
Run REPL with make run/nix/repl
, you will see:
Welcome to Nix version 2.3.15. Type :? for help. Loading '<nixpkgs>'... Added 14696 variables. nix-repl>
Packages are available inside pkgs
namespace, write:
nix-repl> pkgs.hello
Then press TAB
, you will see:
pkgs.hello pkgs.hello-unfree pkgs.hello-wayland
To see package description:
nix-repl> pkgs.hello.meta.description "A program that produces a familiar, friendly greeting"
Nix package manager is able to build containers which conforms OCI format.
We have an example docker container with:
- bash
- curl
- CA certificates
- coreutils + some additional tools
To build this container:
- change your working directory to
./container
withcd ./container
- build a container with
make nix/build/container
This will output ./build/container.tar.gz
symbolic link. This symbolic will point to the object inside /nix/store/
.
To import this .tar.gz
into docker you will need to copy this file from nix store somewhere where it will be accessible to docker:
$ cp -L ./build/container.tar.gz ./container.tar.gz
Then open separate terminal tab and navigate to ./container
directory, after that:
$ docker load -i container.tar.gz
Run container with:
$ docker run -it gitlab.example.com:5050/nix/showcase/showcase:latest