Declarative builds and deployments
codgician
2024.05.20
B-SAT problem (NP-complete).
Implement pseudo-solvers.
When you try to manage the global system,
you lose isolation.
Wait, won’t build + deployment be complex due to monolithic?
Chunking.
But we sacrifice sharing between components.
Can we marry isolation with fine-grained package management?
Originating from The Purely Functional Software Deployment Model (2006),
Nix solves all above problems,
while achieving decalrative builds & deployments.
Deterministic software building makes dependencies deterministic.
\begin{CD} \text{Inputs} @> f>> \text{Output} \end{CD}
\begin{CD} x = 1, y = 2 @> f(x, y) = x + y >> 1 + 2 = 3 \end{CD}
\begin{CD} \text{gcc, libc, source, ...} @> \text{./configure, make, make install} >> \text{binary} \end{CD}
Derivations are stored in Nix Store, and builds output to Nix Store.
A derivation’s build inputs are also managed in Nix Store.
Nix store paths made unique with cryptographic hash:
/nix/store/zrwzkd3szh13zd3wrlzj0kdkgiv1xzjn-hello.drv
/nix/store/rq6w0k38h7kbh2s9snwpysk5yph2fqbf-hello
The output path’s hash is generated by derivation content.
Nix store is read-only, only modifiable by
nix
.
/lib
, /usr/bin
, etc./proc
,
/dev
, /dev/shm
and /dev/pts
(Linux-only).
An example “hello world” program:
… and how we write derivation for it:
{
"/nix/store/87zf1q5dx3dkn597lqq17f1g83y116l6-hello.drv": {
"args": [ "-e", "/nix/store/v6x3cs394jgqfbi0a42pam708flxaphh-default-builder.sh" ],
"builder": "/nix/store/0c337gsdfjf3162avbkchh0yh4qbs2s3-bash-5.2-p15/bin/bash",
"env": {
"buildPhase": "gcc main.c -o hello\n",
"builder": "/nix/store/0c337gsdfjf3162avbkchh0yh4qbs2s3-bash-5.2-p15/bin/bash",
"installPhase": "mkdir -p $out/bin\ncp hello $out/bin\n",
"name": "hello",
"nativeBuildInputs": "/nix/store/hc326d04c91h73ndqdx3qkggsk730kf2-gcc-wrapper-12.3.0",
"out": "/nix/store/8ygc7ks9ggj7p2q0b98w1axc3mkyi68c-hello",
"outputs": "out",
"src": "/nix/store/92m3yxqi2hfmj75b053zvj0kkhv9bplq-src",
"stdenv": "/nix/store/iszb73m627pq8v3gwf7zl6xaw01ln2hj-stdenv-linux",
"system": "aarch64-linux"
},
// to be continued...
}}
{{ // continuing
"inputDrvs": {
"/nix/store/f27pfz65b77lby39rrr48ps21pa6mbxj-gcc-wrapper-12.3.0.drv": {
"outputs": [ "out" ]
},
"/nix/store/hq9032m10smw5qbig1b1cvvqirv61j54-stdenv-linux.drv": {
"outputs": [ "out" ]
},
"/nix/store/nsn38mpj8j5h9861w5chg40f2vz4blq3-bash-5.2-p15.drv": {
"outputs": [ "out" ]
}
},
"inputSrcs": [
"/nix/store/92m3yxqi2hfmj75b053zvj0kkhv9bplq-src",
"/nix/store/v6x3cs394jgqfbi0a42pam708flxaphh-default-builder.sh"
],
"name": "hello",
"outputs": {
"out": { "path": "/nix/store/8ygc7ks9ggj7p2q0b98w1axc3mkyi68c-hello" }
},
"system": "aarch64-linux"
}
}
Another example of hello world with ncurses
… ncurses
needs to be explictly specified as
buildInputs
:
{
"/nix/store/2w3jmr0s30ylyvpri0m2kb91q4c6wvcb-hello.drv": {
"args": [ "-e", "/nix/store/v6x3cs394jgqfbi0a42pam708flxaphh-default-builder.sh" ],
"builder": "/nix/store/0c337gsdfjf3162avbkchh0yh4qbs2s3-bash-5.2-p15/bin/bash",
"env": {
"buildInputs": "/nix/store/k7wlgnj0d7fp3862gy0s5s6vphkm48k1-ncurses-6.4-dev",
"buildPhase": "gcc main.c -o hello -lncurses\n",
"builder": "/nix/store/0c337gsdfjf3162avbkchh0yh4qbs2s3-bash-5.2-p15/bin/bash",
"installPhase": "mkdir -p $out/bin\ncp hello $out/bin\n",
"name": "hello",
"nativeBuildInputs": "/nix/store/hc326d04c91h73ndqdx3qkggsk730kf2-gcc-wrapper-12.3.0",
"out": "/nix/store/rmkrazrqfy8zpk1h52qnjrj9qlcyh9mv-hello",
"src": "/nix/store/yf2fijnfz19kqh8finky3n2rk11217r9-src",
"stdenv": "/nix/store/iszb73m627pq8v3gwf7zl6xaw01ln2hj-stdenv-linux",
"system": "aarch64-linux"
},
}}
{{ // continuing
"inputDrvs": {
"/nix/store/5l9mg0nlx3j0nf08hlaspnnx592acfm1-ncurses-6.4.drv": {
"outputs": [ "dev" ]
},
"/nix/store/f27pfz65b77lby39rrr48ps21pa6mbxj-gcc-wrapper-12.3.0.drv": {
"outputs": [ "out" ]
},
"/nix/store/hq9032m10smw5qbig1b1cvvqirv61j54-stdenv-linux.drv": {
"outputs": [ "out" ]
},
"/nix/store/nsn38mpj8j5h9861w5chg40f2vz4blq3-bash-5.2-p15.drv": {
"outputs": [ "out" ]
}
},
"inputSrcs": [
"/nix/store/v6x3cs394jgqfbi0a42pam708flxaphh-default-builder.sh",
"/nix/store/yf2fijnfz19kqh8finky3n2rk11217r9-src"
],
"name": "hello",
"outputs": {
"out": { "path": "/nix/store/rmkrazrqfy8zpk1h52qnjrj9qlcyh9mv-hello" }
},
"system": "aarch64-linux"
}
}
What if we call other binaries in source?
Create a wrapper to set PATH
before actually
executing the binary
{ pkgs, ... }:
pkgs.stdenv.mkDerivation {
name = "hello";
src = ./src;
nativeBuildInputs = with pkgs; [ gcc makeWrapper ];
buildPhase = ''
gcc main.c -o hello
'';
installPhase = ''
mkdir -p $out/bin
cp hello $out/bin
'';
postFixup = ''
wrapProgram $out/bin/hello \
--prefix PATH : "${pkgs.lib.makeBinPath [ pkgs.cowsay ]}"
'';
}
/bin/hello
is a wrapper script instead of real
binary
$ cat /nix/store/z7i77wwagy58f6svxc8ksm5snsc8wnrm-hello/bin/hello
#! /nix/store/0c337gsdfjf3162avbkchh0yh4qbs2s3-bash-5.2-p15/bin/bash -e
PATH=${PATH:+':'$PATH':'}
PATH=${PATH/':''/nix/store/klqwsfd2xn14bb977d5dvjqdjpp6ka74-cowsay-3.7.0/bin'':'/':'}
PATH='/nix/store/klqwsfd2xn14bb977d5dvjqdjpp6ka74-cowsay-3.7.0/bin'$PATH
PATH=${PATH#':'}
PATH=${PATH%':'}
export PATH
exec -a "$0" "/nix/store/z7i77wwagy58f6svxc8ksm5snsc8wnrm-hello/bin/.hello-wrapped" "$@"
There are many languages/frameworks, complicated.
It seems to restrictive for majority packages to onboard?
Take rust-analyzer as a real-life example.
Build makes little sense if we cannot install it.
Two possibilities:
How to make the package accessible?
Garbage collection.
A working system = Software + Configurations
Why separate packages and configurations?
NixOS leverages Nix to manage both altogether.
/
├── boot
├── bin
├── etc
├── lib
├── usr
│ ├── bin
│ ├── include
│ ├── lib
│ └── share
However, in NixOS …
/
├── boot
├── nix
│ ├── store
│ │ ├── acr...kl-nixos-system-...
│ │ ├── 11w...cv-nixos-system-...
│ │ │ ├── activate
│ │ │ ├── kernel -> /nix/store/bcd...3h-linux-6.6.30/bzImage
│ │ │ ├── systemd -> /nix/store/9cx...8s-systemd-255.4
│ │ │ ├── ...
│ │ ├── bcd...3h-linux-6.6.30
│ │ ├── 9cx...8s-systemd-255.4
│ │ ├── ...
│ ├── var/nix/profiles
│ │ ├── system -> system-250-link
│ │ ├── system-249-link -> /nix/store/acr...kl-nixos-system-...
│ │ ├── system-250-link -> /nix/store/11w...cv-nixos-system-...
/nix/var/nix/profiles/system/activate
Declaratively describe your system in Nix:
{ config, lib, pkgs, ...}: {
# Use systemd-boot EFI bootloader
boot.loader.systemd-boot.enable = true;
# Kernel configurations
boot.supportedFilesystems = [ "bcachefs" ];
boot.initrd.availableKernelModules = [ "xhci_pci" "sr_mod" ];
boot.kernelPackages = pkgs.linuxPackages_latest;
# Use zsh for shell
programs.zsh.enable = true;
programs.zsh.enableCompletion = true;
# Enable the OpenSSH daemon.
services.openssh.enable = true;
# Configure users
users.users.codgi = {
name = "codgician";
home = "/home/codgi";
shell = pkgs.zsh;
openssh.authorizedKeys.keys = [ "..." ];
};
}
The power of NixOS roots in the Nix language.
/home
declaratively
using Nix.Nix/NixOS is still gaining increasing visibility and popularity.
To get started, or learn further about Nix/NixOS:
Slides are
generated by pandoc,
rendered by reveal.js,
and managed by Nix.
Fully open-sourced.
Slides not shown by default
Wait, for hello-ncurses
, doesn’t gcc
do
dynmaic linking by default?
stdenv
.
/lib
, /usr/lib
, etc.-rpath
flag for every
library directory mentioned through -L flags.Wait, how can Nix magically know runtime dependencies?
“Runtime dependencies must be a subset of build time dependencies”.
How can this be true and how can it work?
It just works! 🤪
Wait, what if I have secrets in my configurations?
Nix store is globally readable, any user has R/O access.