Acknowledgment
A few pages are copied from the Scrive Nix Workshop, therefore the following license:
License
Copyright (c) 2024 Tomas Krupka
Copyright (c) 2020-2021 Scrive AB
This work is licensed under both the Creative Commons Attribution-ShareAlike 4.0 International License and the MIT license.
Motivating examples
Specifying dependencies
RUN apt-get update && \
apt-get install -y \
libssl1.1 \
libjson-glib-1.0-0 \
libgstreamer1.0-0 \
gstreamer1.0-tools \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-libav \
libgstrtspserver-1.0-0 \
libjansson4 \
make \
git \
wget \
checkinstall
Dependency hell + destructive updates
https://en.wikipedia.org/wiki/Dependency_hell
tomas.krupka@yersinia:~$ pip install awscli
...
Collecting awscli
Collecting s3transfer<0.11.0,>=0.10.0
Collecting botocore==1.34.23
...
Installing collected packages: botocore, s3transfer, awscli
Attempting uninstall: botocore
Found existing installation: botocore 1.31.30
Uninstalling botocore-1.31.30:
Successfully uninstalled botocore-1.31.30
...
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
WARNING: boto3 1.28.30 requires botocore<1.32.0,>=1.31.30, but you have botocore 1.34.23 which is incompatible.
WARNING: boto3 1.28.30 requires s3transfer<0.7.0,>=0.6.0, but you have s3transfer 0.10.0 which is incompatible.
Successfully installed awscli-1.32.23 botocore-1.34.23 s3transfer-0.10.0
Multiple software versions and variants
pip install conan==1.59
pip install conan==2.0
apt install opencv
apt install opencv-with-cuda
What happens if an update fails?
- The update process itself fails:
apt-get update && apt-get upgrade
...
CTRL+C
- The result of the update is undesired
error: invalid magic number
error: you need to load kernel first
Design goals
Nix: A Safe and Policy-Free System for Software Deployment
Existing systems for software deployment are neither safe nor sufficiently flexible. Primary safety issues are the inability to enforce reliable specification of component dependencies, and the lack of support for multiple versions or variants of a component. This renders deployment operations such as upgrading or deleting components dangerous and unpredictable.
The primary features of Nix are:
- Concurrent installation of multiple versions and variants
- Atomic upgrades and downgrades
- Multiple user environments
- Safe dependencies
- Complete deployment
- Transparent binary deployment as an optimisa- tion of source deployment
- Safe garbage collection
- Multi-level package management (i.e., centralised + local)
- Portability
Safe and flexible software deployment
Software deployment is the act of transferring software to the environment where it is to be used. This is a deceivingly hard problem: a number of requirements make effective software deployment difficult in practice, as most current systems fail to be sufficiently safe and flexible.
The main safety issue that a software deployment system must address is consistency: no deployment action should bring the set of installed software components into an inconsistent state. For instance, an installed component should never be able to refer to any component not present in the system; and upgrading or removing components should not break other components or running programs, e.g., by overwriting the files of those components. In particular, it should be possible to have multiple versions and variants of a component installed at the same time.
What is Nix?
Programming Language
-
Dynamically typed - Similar semantics to JavaScript and Lisp.
-
Functional programming - Higher order functions, immutability, etc.
-
Lazy - Values are not evaluated until needed.
Package Manager
-
Packages as special Nix objects that produce derivations and build artifacts.
-
One package can serve as build input of another package.
-
Multiple versions of the "same" package can be present on the same system.
Build System
-
Packages are built from source code.
-
Build artifacts of packages are cached based on content address (SHA256 checksum).
-
Multi language / multi repository build system.
- Language agnostic.
- Construct your own build system pipeline.
Operating System
-
Nix itself is a pseudo operating system.
- Rich set of Nix packages that can typically be found in OS packages.
-
Nix packages can co-exist non-destructively with native OS packages.
-
All Nix artifacts are stored in
/nix
. -
Global "installation" is merely a set of symlinks to Nix artifacts in
/nix/store
.
-
-
Lightweight activation of global Nix packages.
-
Add
~/.nix-profile/bin/
to$PATH
. -
Call
source ~/.nix-profile/etc/profile.d/nix.sh
to activate Nix. -
Otherwise Nix is almost invisible to users if it is not activated.
-
-
NixOS is a full Linux operating system.
Reproducibility
-
Key differentiation of Nix as compared to other solutions.
-
Nix packages are built inside a lightweight sandbox.
-
No containerization.
-
Sanitize all environment variables.
-
Special
$HOME
directory at/homeless-shelter
. -
Reset date to Unix time 0.
-
Very difficult to accidentally escape the sandbox.
-
-
Content-addressable storage.
-
Addresses of Nix packages are based on a checksum of the source code, plus other factors such as CPU architecture and operating system.
-
If the checksum of the source code changes, the addresses of the derivation and any build artifacts also change.
-
If the address of a dependency changes, the addresses of the derivation and build artifact also change.
-
Installation
Download available at https://nixos.org/download.html.
Simplest way is to run this on Linux:
sh <(curl -L https://nixos.org/nix/install) --daemon
On MacOS:
sh <(curl -L https://nixos.org/nix/install)
After installation, you might need to relogin to your shell to reload the environment. Otherwise, run the following to use Nix immediately:
source ~/.nix-profile/etc/profile.d/nix.sh
Update
If you have installed Nix before but have not updated it for a while, you should update it with:
nix-channel --update
This helps ensure we are installing the latest version of packages in global installation and global imports.
Learning Resources for Nix
-
- Nix Manual - Information about
nix
-the-command and the Nix Language. - Nixpkgs Manual - Information about the Nix Package Set (
nixpkgs
). How to extend and customise packages, how each language ecosystem is packaged, etc. - NixOS Manual - Information about the NixOS operating system. How to install/configure/update NixOS, the DSL for describing configuration options, etc.
- Nix Manual - Information about
-
nix.dev - Pragmatic guide on how to use Nix productively.
-
Awesome Nix - Curated list of Nix resources.
-
Nix Pills - Alternative Nix tutorial. Takes a bottom-up approach, explaining Nix and Nixpkgs design patterns along the way.
-
Nix Whitepaper - Nix: A Safe and Policy-Free System for Software Deployment
Useful links
- Download
- Search packages
- Search NixOS options
- Nix Channel Status
- Nix builtin functions
- Nixpkgs manual (search for lib functions)
External projects
Examples
Nix Shell
You can use Nix packages without installing them globally on your machine. This is a good way to bring in the tools you need for individual projects.
$ nix-shell -p hello
[nix-shell:nix-workshop]$ hello
Hello, world!
Using Multiple Packages
$ nix-shell -p python3 htop vim
[nix-shell:nix-workshop]$ which python3
/nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6/bin/python3
[nix-shell:nix-workshop]$ which htop
/nix/store/i4af69js47rqcym6j6y83nxj58ihsi96-htop-3.2.2/bin/htop
[nix-shell:nix-workshop]$ which vim
/nix/store/jiixxdyqlaalxzzs0nzhpprgy6rx2bxg-vim-9.0.2048/bin/vim
Using shell.nix
It is common practice for Nix-using projects to provide a shell.nix
file that specifies the shell environment. The nix-shell
command reads this file, allowing us to create reproducible shell environments without using -p
. These environments can provide access to any tool written in any language, without polluting the global environment. We will cover the use of shell.nix
in a later chapter.
Nix Dependencies
Check out a package in a Nix Shell:
nix-shell -p subversion graphviz
Runtime dependencies
Print its runtime dependencies (closure):
nix-store --query --requisites $(which svn)
Build dependencies
Print the build-time dependencies of svn
:
derivation = $(nix-store --query --valid-derivers $(which svn))
nix-store --query --requisites $derivation
Dependency graph
Plot a graph with Graphviz:
nix-store --query --graph $(which svn) | dot -Tpdf > graph.pdf
Learn more
https://nixos.org/manual/nix/stable/command-ref/nix-store/query#examples
Nix Language Primitives
nix repl
Welcome to Nix 2.18.1. Type :? for help.
nix-repl> "Hello World!"
"Hello World!"
Strings
nix-repl> "hello"
"hello"
Booleans
nix-repl> true
true
nix-repl> false
false
nix-repl> true && false
false
nix-repl> true || false
true
Null
nix-repl> null
null
nix-repl> true && null
error: value is null while a Boolean was expected, at (string):1:1
Numbers
nix-repl> 1
1
nix-repl> 2
2
nix-repl> 1 + 2
3
String Interpolation
nix-repl> name = "John"
nix-repl> name
"John"
nix-repl> "Hello, ${name}!"
"Hello, John!"
Multiline Strings
nix-repl> ''
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nullam augue ligula, pharetra quis mi porta.
- ${name}
''
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Nullam augue ligula, pharetra quis mi porta.\n\n- John\n"
String Concatenation
nix-repl> "Hello " + "World"
"Hello World"
nix-repl> "Hello " + 123
error: cannot coerce an integer to a string, at (string):1:1
Set / Object
nix-repl> object = { foo = "foo val"; bar = "bar val"; }
nix-repl> object
{ bar = "bar val"; foo = "foo val"; }
nix-repl> object.foo
"foo val"
nix-repl> object.bar
"bar val"
Merge Objects
nix-repl> a = { foo = "foo val"; bar = "bar val"; }
nix-repl> b = { foo = "override"; baz = "baz val"; }
nix-repl> a // b
{ bar = "bar val"; baz = "baz val"; foo = "override"; }
Inherit
nix-repl> foo = "foo val"
nix-repl> bar = "bar val"
nix-repl> { foo = foo; bar = bar; }
{ bar = "bar val"; foo = "foo val"; }
nix-repl> { inherit foo bar; }
{ bar = "bar val"; foo = "foo val"; }
List
nix-repl> list = [ "hello" 123 { foo = "foo"; } ]
nix-repl> list
[ "hello" 123 { ... } ]
List Concatenation
nix-repl> [ 1 2 ] ++ [ "foo" "bar" ]
[ 1 2 "foo" "bar" ]
Nix Expressions
If Expression
nix-repl> if true then 0 else 1
0
nix-repl> if false then 0 else "foo"
"foo"
nix-repl> if null then 1 else 2
error: value is null while a Boolean was expected
Let Expression
nix-repl> let
foo = "foo val";
bar = "bar val, ${foo}";
in
{ inherit foo bar; }
{ bar = "bar val, foo val"; foo = "foo val"; }
With Expression
nix-repl> let
object = {
foo = "foo val";
bar = "bar val";
};
in
with object; [
foo
bar
]
[ "foo val" "bar val" ]
Function
nix-repl> greet = name: "Hello, ${name}!"
nix-repl> greet "Alice"
"Hello, Alice!"
nix-repl> greet "Bob"
"Hello, Bob!"
Curried Function
nix-repl> secret-greet = code: name:
if code == "secret"
then "Hello, ${name}!"
else "Nothing here"
nix-repl> secret-greet "secret" "John"
"Hello, John!"
nix-repl> nothing = secret-greet "wrong"
nix-repl> nothing "Alice"
"Nothing here"
nix-repl> nothing "Bob"
"Nothing here"
Named Arguments
nix-repl> greet = { name, title }: "Hello, ${title} ${name}"
nix-repl> greet { title = "Ms."; name = "Alice"; }
"Hello, Ms. Alice"
nix-repl> greet { name = "Alice"; }
error: anonymous function at (string):1:2 called without required argument 'title', at (string):1:1
Accepting unknown arguments
nix-repl> greet = { name, ... }: "Hello, ${name}"
nix-repl> greet { name = "Alice"; unused = "whatever"; }
"Hello, Alice"
Default Arguments
nix-repl> greet = { name ? "Anonymous", title ? "Ind." }: "Hello, ${title} ${name}"
nix-repl> greet {}
"Hello, Ind. Anonymous"
nix-repl> greet { name = "Bob"; }
"Hello, Ind. Bob"
nix-repl> greet { title = "Mr."; }
"Hello, Mr. Anonymous"
Lazy Evaluation
nix-repl> err = throw "something went wrong"
nix-repl> err
error: something went wrong
nix-repl> if true then 1 else err
1
nix-repl> if false then 1 else err
error: something went wrong
nix-repl> object = { foo = err; bar = "bar val"; }
nix-repl> object.bar
"bar val"
nix-repl> object.foo
error: something went wrong
Library functions
Builtins
These are available by default in the Nix language, reference: Nix manual
String to File
nix-repl> builtins.toFile "hello.txt" "Hello World!"
"/nix/store/r4mvpxzh7rgrm4j831b2yi90zq64grqm-hello.txt"
$ cat /nix/store/r4mvpxzh7rgrm4j831b2yi90zq64grqm-hello.txt
Hello World!
Read File
nix-repl> builtins.readFile ./code/03-nix-basics/03-files/hello.txt
"Hello World!"
nix-repl> builtins.readFile /nix/store/r4mvpxzh7rgrm4j831b2yi90zq64grqm-hello.txt
"Hello World!"
nix-repl> builtins.readFile (builtins.toFile "hello" "Hello World!")
"Hello World!"
Fetch URL
nix-repl> example = builtins.fetchurl "https://scrive.com/robots.txt"
nix-repl> example
[0.0 MiB DL] downloading 'https://scrive.com/robots.txt'"/nix/store/r98i29hkzwyykm984fpr4ldbai2r8lhj-robots.txt"
nix-repl> example
"/nix/store/r98i29hkzwyykm984fpr4ldbai2r8lhj-robots.txt"
$ cat /nix/store/r98i29hkzwyykm984fpr4ldbai2r8lhj-robots.txt
User-agent: *
Sitemap: https://scrive.com/sitemap.xml
Disallow: /amnesia/
Disallow: /api/
URLs are only fetched once locally!
Fetch Tarball
nix-repl> nodejs-src = builtins.fetchTarball
"https://nodejs.org/dist/v14.15.0/node-v14.15.0-linux-x64.tar.xz"
nix-repl> nodejs-src
"/nix/store/6wkj0blipzdqbsvwv03qy57n4l33scpw-source"
$ ls /nix/store/6wkj0blipzdqbsvwv03qy57n4l33scpw-source
bin CHANGELOG.md include lib LICENSE README.md share
Nixpkgs library functions
These have to be imported from the nixpkgs repository, reference: Nixpkgs manual
nix-repl> :l <nixpkgs>
nix-repl> object = { a = 1; b = 2; c = true; }
nix-repl> lib.attrNames object
[ "a" "b" "c" ]
nix-repl> lib.attrValues object
[ 1 2 true ]
Finding the main executable of a program:
nix-repl> lib.getExe mpv
"/nix/store/rw37dfsb0sijl5ld7ihc17295xdr66q5-mpv-with-scripts-0.36.0/bin/mpv"
Custom Nix Shell
Python
Example is forked from Setting up a Python development environment, having the following app myapp.py
:
#!/usr/bin/env python
import random
import subprocess
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
result = subprocess.run(
["cowsay", random.choice(["Nix rules!"] * 6 + ["Nix sucks!"])],
capture_output=True,
)
result.check_returncode()
return {"message": result.stdout.decode()}
def run():
app.run(host="0.0.0.0", port=5000)
if __name__ == "__main__":
run()
Let's create an evironment with app dependencies + extra developer tools, named myapp.nix
:
{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
packages = with pkgs; [
(python3.withPackages (ps: [ ps.flask ]))
curl
jq
cowsay
];
}
Enter the shell and run the app:
nix-shell myapp.nix
python ./myapp.py &
curl 127.0.0.1:5000 | jq -r '.message'
C++
Let's run the Boost Python hello world, filename boost.cc
:
#include <boost/python.hpp>
char const* greet()
{
return "hello, world";
}
BOOST_PYTHON_MODULE(hello_ext)
{
boost::python::def("greet", greet);
}
Standard CMakeLists.txt
file:
cmake_minimum_required(VERSION 3.5)
project(BoostPythonHello)
find_package(Python 3 REQUIRED COMPONENTS Development)
find_package(Boost COMPONENTS python REQUIRED)
set(CMAKE_SHARED_MODULE_PREFIX "")
add_library(hello_ext MODULE boost.cc)
target_link_libraries(hello_ext PRIVATE Boost::python Python::Module)
Nix environment boost.nix
, notice that we override the default boost package options to get python support:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs;
let
myPython = python3.withPackages (ps: [ ps.matplotlib ps.numpy ]);
in
[
(boost.override {
enablePython = true;
enableNumpy = true;
python = myPython;
})
myPython
cmake
];
}
Build and run:
nix-shell boost.nix
cmake -S . -B build
cmake --build build
cd build
python3
>>> import hello_ext
>>> hello_ext.greet()
'hello, world'
Custom Python package 1
Let's package the cowsay server from before, so that others can use it in their environments. This is done using the stdenv.mkDerivation, a package is a function defined in a .nix
file taking all the build inputs and outputting its results in a special $out
location.
First try:
{ stdenv
, python3
}:
stdenv.mkDerivation rec {
pname = "cowsayer";
version = "0.0.1";
src = [
./myapp.py
];
dontUnpack = true;
# can be used at build time only
nativeBuildInputs = [ ];
# can be used at build time and run time
buildInputs = [
(python3.withPackages (ps: [ ps.flask ]))
];
buildPhase = ''
sleep 1
'';
installPhase = ''
install -m755 -D ${./myapp.py} $out/bin/cowsayer-server
'';
}
Let's try building it:
nix-build myapp_v1.nix
Better:
nix-build -E 'with import <nixpkgs> {}; callPackage ./myapp_v1.nix {}'
Test:
./result/bin/cowsayer-server
nix-shell -p curl jq
curl 127.0.0.1:5000 | jq -r '.message'
Custom Python package 2
Second try:
{ stdenv
, python3
, cowsay
, makeWrapper
, lib
}:
stdenv.mkDerivation rec {
pname = "cowsayer";
version = "0.0.2";
src = [
./myapp.py
];
dontUnpack = true;
# can be used at build time only
nativeBuildInputs = [
makeWrapper
];
# can be used at build time and run time
buildInputs = [
(python3.withPackages (ps: [ ps.flask ]))
];
buildPhase = ''
sleep 1
'';
installPhase = ''
install -m755 -D ${./myapp.py} $out/bin/cowsayer-server
wrapProgram $out/bin/cowsayer-server \
--prefix PATH : ${lib.makeBinPath [ cowsay ]}
'';
}
Build:
nix-build -E 'with import <nixpkgs> {}; callPackage ./myapp_v2.nix {}'
Test:
./result/bin/cowsayer-server
nix-shell -p curl jq
curl 127.0.0.1:5000 | jq -r '.message'
Custom Python package 3
Second try:
{ stdenv
, python3
, cowsay
, makeWrapper
, lib
, writeShellApplication
, curl
, jq
, verbose ? false
}:
stdenv.mkDerivation rec {
pname = "cowsayer";
version = "0.0.2";
src = [
./myapp.py
];
dontUnpack = true;
# can be used at build time only
nativeBuildInputs = [
makeWrapper
];
# can be used at build time and run time
buildInputs = [
(python3.withPackages (ps: [ ps.flask ]))
];
buildPhase = ''
sleep 1
'';
installPhase =
let
client = writeShellApplication {
name = "nixcow";
runtimeInputs = [ curl jq ];
text = ''
curl 127.0.0.1:5000 ${lib.optionalString (!verbose) "-s"} | jq -r '.message'
'';
};
in
''
install -m755 -D ${./myapp.py} $out/bin/cowsayer-server
wrapProgram $out/bin/cowsayer-server \
--prefix PATH : ${lib.makeBinPath [ cowsay ]}
ln -s ${lib.getExe client} $out/bin/nixcow
'';
}
Build:
nix-build -E 'with import <nixpkgs> {}; callPackage ./myapp_v3.nix { verbose = true; }'
Test:
./result/bin/cowsayer-server
./result/bin/nixcow
C++ package
Let's package the following app: https://github.com/krupkat/microbench_test in a bench.nix
file
{ stdenv
, cmake
, boost
, gbenchmark
, fetchurl
}:
stdenv.mkDerivation rec {
pname = "microbench_test";
version = "0.0.2";
src = fetchurl {
url = "https://github.com/krupkat/${pname}/archive/refs/tags/${version}.tar.gz";
sha256 = "0gzzi3bg4yrlcmimjib2hz6r5djk5v3y7a0azcpypch90a9f2k1i";
};
# can be used at build time only
nativeBuildInputs = [
cmake
];
# can be used at build time and run time
buildInputs = [
boost
gbenchmark
];
}
Build and run:
nix-build -E 'with import <nixpkgs> {}; callPackage ./bench.nix {}'
./result/bin/bench
note on the Nix language, why?
Package overriding 1
A powerful feature if Nix is the option to override any packages inputs (docs).
Let's use this to compare multiple builds of our benchmark application. Create the following multibench_v1.nix
shell file:
{ pkgs ? import <nixpkgs> { }
, lib ? pkgs.lib
}:
let
args = lib.cartesianProductOfSets {
cxxstd = [ "11" "20" ];
stdenv = [ pkgs.gcc13Stdenv pkgs.llvmPackages_17.stdenv ];
};
overrideBoost = { cxxstd, stdenv }: {
name = "std${cxxstd}-${stdenv.cc.name}";
stdenv = stdenv;
boost = pkgs.boost.override {
extraB2Args = [ "cxxstd=${cxxstd}" ];
stdenv = stdenv;
};
};
boostList = map overrideBoost args;
customizeBench = { name, stdenv, boost }:
pkgs.writeShellApplication {
name = "bench-${name}";
runtimeInputs = [
(pkgs.callPackage ./bench.nix {
inherit stdenv boost;
})
];
text = "bench";
};
in
pkgs.mkShell rec {
packages = map customizeBench boostList;
shellHook =
let
runTest = test: ''
echo "Running ${test.name}:"
${test.name}
'';
in
lib.concatMapStringsSep "\n" runTest packages;
}
And test it:
nix-shell multibench_v1.nix
bench-std11-clang-wrapper-17.0.6
bench-std20-gcc-wrapper-13.2.0
Package overriding 2
On top of overriding package inputs, we can also override any of its attributes. Those can be either the existing ones (like src
) or we can add new ones, like one of the many hooks (preBuild
, postInstall
, ... check the list of build phases).
We use this modify the source code to run the benchmark on a larger hashmap.
There is also additional shellHook
that auto runs all the benchmarks when entering the shell. The file is named multibench_v2.nix
:
{ pkgs ? import <nixpkgs> { }
, lib ? pkgs.lib
}:
let
args = lib.cartesianProductOfSets {
cxxstd = [ "11" "20" ];
stdenv = [ pkgs.gcc13Stdenv pkgs.llvmPackages_17.stdenv ];
};
overrideBoost = { cxxstd, stdenv }: {
name = "std${cxxstd}-${stdenv.cc.name}";
stdenv = stdenv;
boost = pkgs.boost.override {
extraB2Args = [ "cxxstd=${cxxstd}" ];
stdenv = stdenv;
};
};
boostList = map overrideBoost args;
customizeBench = { name, stdenv, boost }:
pkgs.writeShellApplication {
name = "bench-${name}";
runtimeInputs =
let
test = pkgs.callPackage ./bench.nix {
inherit stdenv boost;
};
in
[
(test.overrideAttrs
(final: prev: {
preConfigure = ''
substituteInPlace benchmark.cc --replace "10000" "1000000"
'';
}))
];
text = "bench";
};
in
pkgs.mkShell rec {
packages = map customizeBench boostList;
shellHook =
let
runTest = test: ''
echo "Running ${test.name}:"
${test.name}
'';
in
lib.concatMapStringsSep "\n" runTest packages;
}
Build and run:
nix-shell multibench_v2.nix
Nix channels and pinning
Where do the packages come from?
nix repl
nix-repl> :l <nixpkgs>
opencv
echo $NIX_PATH
List channels:
nix-channel --list
nixos https://nixos.org/channels/nixos-23.11
nixos-unstable https://nixos.org/channels/nixos-unstable
sops-nix https://github.com/Mic92/sops-nix/archive/master.tar.gz
Load a specific channel:
nix-repl> :l <nixos-unstable>
Mix channels in your environment:
{ pkgs ? import <nixos> { }
, pkgs-unstable ? import <nixos-unstable> {}
}:
pkgs.mkShell {
packages = [
pkgs.vlc
pkgs-unstable.vlc
];
}
Pin your dependencies:
https://nix.dev/tutorials/first-steps/towards-reproducibility-pinning-nixpkgs.html
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/5f5210aa20e343b7e35f40c033000db0ef80d7b9.tar.gz") {}
}:
pkgs.mkShell {
packages = [
pkgs.vlc
];
}
Picking the commit can be done via status.nixos.org, which lists all the releases and the latest commit that has passed all tests.
NixOS
Taking the ideas of the Nix package manager and applying them to the whole operating system.
The whole system is defined in a configuration file, usually placed in /etc/nixos/configuration.nix
.
This is a minimal configuration.nix
example:
{ config, pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
services.xserver.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
users.users.alice = {
isNormalUser = true;
extraGroups = [ "wheel" ];
packages = with pkgs; [
firefox
vlc
];
};
system.stateVersion = "23.11";
}
With hardware-configuration.nix
being specific to a computer and usually autogenerated by nixos-generate-config
:
{ config, lib, pkgs, modulesPath, ... }:
{
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "usbhid" "usb_storage" "sd_mod" "sdhci_pci" ];
boot.kernelModules = [ "kvm-amd" ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/6132cd3c-aba6-4f51-9360-a064e98f6942";
fsType = "ext4";
};
boot.initrd.luks.devices."luks-fde45edd-d98c-4e9e-aa8a-541266584cc0".device = "/dev/disk/by-uuid/fde45edd-d98c-4e9e-aa8a-541266584cc0";
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/FADD-B2BE";
fsType = "vfat";
};
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
Options
All available options are searchable on https://search.nixos.org/options, they are implemented in the nixpkgs repository (https://github.com/NixOS/nixpkgs/tree/master/nixos).
Rebuilding
Build the system and switch to the new configuration:
sudo nixos-rebuild switch
no manual sudo, all changes saved in git
NixOS + overriding
Let's customize one of the installed packages in the system (vlc);
{ config, pkgs, callPackage, ... }:
{
imports = [ ./hardware-configuration.nix ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
services.xserver.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
users.users.alice = {
isNormalUser = true;
extraGroups = [ "wheel" ];
packages = with pkgs;
let
customFFMpeg = callPackage ./build_my_ffmpeg.nix {};
[
firefox
(vlc.override { ffmpeg_4 = customFFMpeg; })
];
};
system.stateVersion = "23.11";
}
Benefit: custom system components + up to date versions.
Custom modules
Separate functionality in custom modules and reuse where needed by importing + passing options.
zswap module
{ config, lib, pkgs, ... }:
let cfg = config.krupkat.system.zswap;
in {
options.krupkat.system.zswap.enable = lib.mkEnableOption "Zswap";
config = lib.mkIf cfg.enable {
boot = {
initrd = {
kernelModules = [ "lz4" "z3fold" ];
preDeviceCommands = ''
printf lz4 > /sys/module/zswap/parameters/compressor
printf z3fold > /sys/module/zswap/parameters/zpool
printf 25 > /sys/module/zswap/parameters/max_pool_percent
'';
};
kernel.sysctl = {
"vm.swappiness" = 180;
"vm.page-cluster" = 0;
};
kernelParams = [ "zswap.enabled=1" ];
};
};
}
Import and enable in the main configuration.nix
file:
{ config, lib, pkgs, ... }:
{
imports =
[
./modules/zswap.nix
];
krupkat.system.zswap.enable = true;
...
}
Home manager
Taking the ideas of the Nix package manager and applying them to a users home folder (Home manager, Home manager options).
{ config, lib, pkgs, ... }:
{
home.username = "tom";
home.homeDirectory = "/home/tom";
home.stateVersion = "23.05";
home.packages = with pkgs; [
atool
cinnamon.nemo
clinfo
dstat
gimp
gnome.file-roller
gnome.gedit
gnome.gnome-system-monitor
gnome.simple-scan
lm_sensors
firefox
gthumb
htop
libarchive
libreoffice
mpv
nixpkgs-fmt
nix-index
nix-prefetch-github
radeontop
rescale
unzip
vlc
wget
xpano
];
home.file = {
".config/dunst".source = ./dunst;
".config/hypr/hyprland.conf".source =
pkgs.substituteAll { src = ./hyprland/template.conf; launch_waybar = ./hyprland/waybar.sh; };
".config/hypr/hyprpaper.conf".source =
pkgs.substituteAll { src = ./hyprpaper/template.conf; wallpaper = ./hyprpaper/nixos.png; };
".config/kitty".source = ./kitty;
".config/rofi".source = ./rofi;
".config/swayidle".source = ./swayidle;
".config/swaylock".source = ./swaylock;
".config/waybar".source = ./waybar;
".config/wireplumber".source = ./wireplumber;
".local/share/gedit".source = ./gedit;
};
programs.home-manager.enable = true;
programs.vscode = {
enable = true;
package = pkgs.vscodium;
extensions = with pkgs; [
vscode-extensions.dracula-theme.theme-dracula
vscode-extensions.jnoortheen.nix-ide
vscode-extensions.llvm-vs-code-extensions.vscode-clangd
vscode-extensions.ms-vscode.cmake-tools
vscode-extensions.twxs.cmake
vscode-extensions.xaver.clang-format
vscode-extensions.vadimcn.vscode-lldb
];
};
programs.git = {
enable = true;
userName = "Tomas Krupka";
userEmail = "6817216+krupkat@users.noreply.github.com";
};
programs.bash.enable = true;
programs.vim = {
enable = true;
extraConfig = ''
set nu
syntax enable
let g:dracula_colorterm = 0
colorscheme dracula
'';
plugins = [ pkgs.vimPlugins.dracula-vim ];
};
gtk = {
enable = true;
theme = {
name = "Dracula";
package = pkgs.dracula-theme;
};
iconTheme = {
name = "Dracula";
package = dracula-icon-theme-custom;
};
};
}
NixOS examples
With the power of the NixOS options, you can customize all the parts of your system without having to switch between contexts / different configuration paradigms / languages.
You can share variables in the Nix configuration across the different components
- e.g. when configuring server, the domain name and specific service ports can be defined at the top of the configuration file and then reused everywhere
- this removes duplication in your system configuration
Boot opions
Latest kernel + kernel parameter + systemd-boot instead of grub
{
boot.kernelPackages = pkgs.linuxPackages_latest;
boot.kernelParams = [ "amd_pstate=active" ];
boot.loader.systemd-boot.enable = true;
}
System packages
Packages available to all system users:
{
environment.systemPackages = with pkgs; [
gnome.seahorse
sshfs
]
}
User management
{
users.users.tom = {
isNormalUser = true;
description = "T.K.";
extraGroups = [ "networkmanager" "wheel" "video" ];
};
users.users.nixremote = {
isNormalUser = true;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFa5xTjWp9+btqQ0hkJiU3gys0xD3/uCXK48ZbzlMvjL"
];
};
}
Install hardware drivers
{
hardware.bluetooth.enable = true;
hardware.opengl.enable = true;
}
Installing fonts
{
fonts.packages = with pkgs; [
font-awesome
nerdfonts
cantarell-fonts
iosevka
];
}
Networking
{
networking = {
hostName = "ideapad";
networkmanager.enable = true;
networkmanager.dns = "none";
nameservers = [ "127.0.0.1" "::1" ];
};
}
Enable services
SSH
{
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
};
}
Builtin services
{
services.fstrim.enable = true;
services.printing.enable = true;
services.gnome.gnome-keyring.enable = true;
services.blueman.enable = true;
services.dnscrypt-proxy2.enable = true;
}
Custom systemd service
{
systemd.services.suspend = {
description = "User suspend actions";
before = [ "sleep.target" ];
wantedBy = [ "sleep.target" ];
serviceConfig = {
ExecStart = "systemd-run --user --machine=tom@ ${pkgs.swaylock}/bin/swaylock";
ExecStartPost = "${pkgs.coreutils}/bin/sleep 1";
};
};
}
Custom systemd timer
{
systemd.timers.inadyn = {
description = "Sync ddns every ${cfg.period}";
wantedBy = [ "default.target" ];
timerConfig = {
OnBootSec = "2m";
OnUnitActiveSec = cfg.period;
};
};
}
Experiment without fear!
- different kernels + kernel arguments
- different gpu drivers
- different OpenCL + Vulkan implementations
- kernel modules (zram + zswap)
- power management
- custom cpu microcode updates
- custom dns server
- get rid of x11
- ...
when you're done... you're done!
Remote builds
NixOS rebuild can be run to build locally and push to a target (docs):
nixos-rebuild --target-host tomaskrupka.cz --use-remote-sudo switch -I nixos-config=configuration.nix
Or to build remotely and deploy locally with --build-host
.
This works across architectures.
Distributed builds
We can setup the nix.buildMachines
option wit ha list of remote builders, e.g.:
{
nix.buildMachines = [ {
hostName = "builder";
system = "x86_64-linux";
maxJobs = 12;
speedFactor = 2;
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
}
...
] ;
}
The rebuild process then transparently chooses the best target to build on based on architecture / priority.
Cross compilation
Copied from the tutorial: https://nix.dev/tutorials/cross-compilation.html
Available build environments
nix repl '<nixpkgs>'
Welcome to Nix version 2.3.12. Type :? for help.
Loading '<nixpkgs>'...
Added 14200 variables.
nix-repl> pkgsCross.<TAB>
pkgsCross.aarch64-android pkgsCross.musl-power
pkgsCross.aarch64-android-prebuilt pkgsCross.musl32
pkgsCross.aarch64-darwin pkgsCross.musl64
pkgsCross.aarch64-embedded pkgsCross.muslpi
pkgsCross.aarch64-multiplatform pkgsCross.or1k
pkgsCross.aarch64-multiplatform-musl pkgsCross.pogoplug4
pkgsCross.aarch64be-embedded pkgsCross.powernv
pkgsCross.amd64-netbsd pkgsCross.ppc-embedded
pkgsCross.arm-embedded pkgsCross.ppc64
pkgsCross.armhf-embedded pkgsCross.ppc64-musl
pkgsCross.armv7a-android-prebuilt pkgsCross.ppcle-embedded
pkgsCross.armv7l-hf-multiplatform pkgsCross.raspberryPi
pkgsCross.avr pkgsCross.remarkable1
pkgsCross.ben-nanonote pkgsCross.remarkable2
pkgsCross.fuloongminipc pkgsCross.riscv32
pkgsCross.ghcjs pkgsCross.riscv32-embedded
pkgsCross.gnu32 pkgsCross.riscv64
pkgsCross.gnu64 pkgsCross.riscv64-embedded
pkgsCross.i686-embedded pkgsCross.scaleway-c1
pkgsCross.iphone32 pkgsCross.sheevaplug
pkgsCross.iphone32-simulator pkgsCross.vc4
pkgsCross.iphone64 pkgsCross.wasi32
pkgsCross.iphone64-simulator pkgsCross.x86_64-embedded
pkgsCross.mingw32 pkgsCross.x86_64-netbsd
pkgsCross.mingwW64 pkgsCross.x86_64-netbsd-llvm
pkgsCross.mmix pkgsCross.x86_64-unknown-redox
pkgsCross.msp430
nix-repl> pkgsCross.aarch64-multiplatform.stdenv.hostPlatform.config
"aarch64-unknown-linux-gnu"
Simple example
let
pkgs = import <nixpkgs> { crossSystem = { config = "aarch64-unknown-linux-gnu"; }; };
in
pkgs.callPackage ../04_benchmark/bench.nix {}
Run:
nix-build simple.nix
./result/bin/bench
Advanced example
let
pkgs = import <nixpkgs> {};
# Create a C program that prints Hello World
helloWorld = pkgs.writeText "hello.c" ''
#include <stdio.h>
int main (void)
{
printf ("Hello, world!\n");
return 0;
}
'';
# A function that takes host platform packages
crossCompileFor = hostPkgs:
# Run a simple command with the compiler available
hostPkgs.runCommandCC "hello-world-cross-test" {} ''
# Wine requires home directory
HOME=$PWD
# Compile our example using the compiler specific to our host platform
$CC ${helloWorld} -o hello
# Run the compiled program using user mode emulation (Qemu/Wine)
# buildPackages is passed so that emulation is built for the build platform
${hostPkgs.stdenv.hostPlatform.emulator hostPkgs.buildPackages} hello > $out
# print to stdout
cat $out
'';
in {
rpi = crossCompileFor pkgs.pkgsCross.raspberryPi;
windows = crossCompileFor pkgs.pkgsCross.mingwW64;
}
Run:
nix-build advanced.nix
cat result
cat result-2
Non-trivial example
VM running on NixOS: https://github.com/krupkat/gcp-nixos.
Hosted at tomaskrupka.cz