Back openDesk Edu for a sovereign, open-source education â every vote counts.
Vote nowSave products you love by clicking the heart icon.
Nix is not all-or-nothing. From a pinned devShell to immutable A/B-OTA appliances â six levels of adopting the Nix spirit, with real experience from K3s clusters, air-gapped registries, and Nix-built mail server containers.
Nix hat den Ruf, schwierig zu sein. Dieser Ruf rĂŒhrt von einem falschen Ausgangspunkt her: Die meisten Tutorials beginnen mit der Nix-Expressionssprache oder mit NixOS, wĂ€hrend die nĂŒtzliche Reise eigentlich mit einem einzigen Befehl in einer Shell beginnt und das Betriebssystem erst erreicht, nachdem man die Vorteile der Reproduzierbarkeit dreimal selbst erlebt hat.
Dies ist der praxisorientierte Begleiter zur Nix Adoption Ladder â wĂ€hrend jener Artikel die Ebenen der Adaption des Nix-Geistes erklĂ€rt, ist dies der konkrete Pfad: Installation, Shells, Flakes, Packaging, Container, NixOS und die Produktionsmuster, die eine gesamte Flotte reproduzierbar machen. Jedes Beispiel stammt aus Mustern, die in echten Deployments laufen: eine in einem Flake fixierte Admin-Toolchain, Container-Images fĂŒr einen Mail-Stack und ein Air-Gapped-Cluster, der Daten aus einem selbst gehosteten Cache bezieht.
Der Standard-Multi-User-Installer ist der richtige Standard fĂŒr Linux und macOS:
sh <(curl -L https://nixos.org/nix/install) --daemon
Der Installer von Determinate Systems (nix-installer) ist die ergonomische Alternative: idempotent, bessere Deinstallation, funktioniert auf mehr Plattformen. In einem Container bietet docker run -it nixos/nix in Sekundenschnelle einen ephemeren Spielplatz â perfekt, um dieses Tutorial zu begleiten.
nix-shell -pnix-shell -p versetzt Sie in eine temporĂ€re Shell mit hinzugefĂŒgten Paketen â ohne dass systemweit etwas installiert wird:
nix-shell -p git jq # shell with git and jq on PATH
nix-shell -p git --run "git --version" # run one command, exit
nix-shell -p git --pure # isolated: no inherited PATH
Halten Sie kurz inne und realisieren Sie, was gerade passiert ist: zwei Tools, fixiert auf eine spezifische nixpkgs-Revision, sofort verfĂŒgbar und weg, sobald Sie die Shell verlassen. Kein apt install, kein âwelche Version ist auf diesem Rechner installiert?â, keine Systemverschmutzung. Dieser eine Befehl ist das gesamte Nix-Konzept im Miniaturformat.
Ad-hoc -p ist fĂŒr die Exploration gedacht. Sobald ein Projekt eine reproduzierbare Toolchain benötigt, halten Sie diese schriftlich fest:
# shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShellNoCC {
packages = [ pkgs.git pkgs.jq ];
GREETING = "Hello";
shellHook = ''
echo "$GREETING â $(git --version)"
'';
}
nix-shell # enter the environment
FĂŒr echte Projekte sollten Sie nixpkgs fixieren, damit âreproduzierbarâ Byte fĂŒr Byte bedeutet und nicht âwas auch immer mein Channel heute enthĂ€ltâ:
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/<sha256>.tar.gz") {} }:
pkgs.mkShell { packages = [ pkgs.kubectl pkgs.kubernetes-helm ]; }
Kombinieren Sie dies mit direnv, und die Umgebung lĂ€dt sich selbst, sobald Sie cd in das Projekt wechseln â die Toolchain ist nun eine Eigenschaft des Verzeichnisses, nicht der Person.
Die Nix-Gewohnheit mit dem höchsten ROI: Fixieren Sie die Admin-Toolchain jedes Repos in einer devShell. Das Referenz-Deployment fĂŒr diesen Artikel fixiert kubectl, helm, helmfile, ansible und yq auf exakte Versionen in einem Flake â dieselbe nix develop funktioniert auf einem Laptop, einem Jump-Host hinter einem Proxy und einem CI-Runner.
Flakes sind der aktuelle Standard: Eine flake.nix im Root des Repos deklariert Inputs (AbhĂ€ngigkeiten, fixiert per Content-Hash in flake.lock) und Outputs (devShells, Pakete, Checks, NixOS-Konfigurationen). Eine Datei, eine Lockfile, ein Befehl fĂŒr alles.
# flake.nix
{
description = "My project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.default = pkgs.mkShell {
packages = [ pkgs.git pkgs.jq ];
};
packages.default = pkgs.hello; # nix build
checks.default = pkgs.hello; # nix flake check
formatter = pkgs.nixfmt; # nix fmt
});
}
Der Workflow:
nix develop # enter the devShell (replaces nix-shell)
nix build # build packages.default â result/
nix run # run it
nix flake check # evaluate AND build every check
nix flake update # bump inputs, rewrite flake.lock
Flake-Best-Practices, die Ihnen echte Debugging-Zeit ersparen werden:
| Practice | Warum |
|---|---|
Commit flake.lock | Fixiert jeden Input auf einen Content-Hash â der Reproduzierbarkeitsvertrag |
let...in statt rec | rec kann unendlich rekursieren und ist schwerer nachvollziehbar |
Kein with am Anfang von Dateien | Zweideutiges Scoping |
Keine <nixpkgs>-Lookup-Pfade in Flakes | Impure â die Lockfile existiert, um Channels zu ersetzen |
config = {}; overlays = []; beim Importieren von nixpkgs | Explizite Purity, keine versehentlichen Overrides |
lib.fileset fĂŒr Quellen | Nur die benötigten Dateien gelangen in den Store; besseres Caching |
git add neue Dateien vor nix build | Flakes sehen nur git-getrackte Dateien |
Run nix fmt via treefmt (nixfmt + statix + deadnix) | Formatierung und Dead-Code-Linting, automatisiert |
stdenv.mkDerivationBeim Packaging hört Nix auf, ein Tool-Manager zu sein, und wird zu einem Build-System. Der Kern ist stdenv.mkDerivation â eine Funktion, die Quellcode + Build-Schritte in einen Store-Pfad verwandelt, dessen Hash jeden Input abdeckt:
{ stdenv, fetchFromGitHub, cmake }:
stdenv.mkDerivation {
pname = "myapp";
version = "1.0.0";
src = fetchFromGitHub {
owner = "me";
repo = "myapp";
rev = "v1.0.0";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
nativeBuildInputs = [ cmake ]; # build-time tools
buildInputs = [ ]; # runtime libraries
installPhase = ''
mkdir -p $out/bin
install -m755 myapp $out/bin/
'';
}
Zwei Eigenschaften machen dies fundamental anders als ein Makefile:
~/.cache, keine System-Libraries, kein âauf meinem Rechner hat es funktioniertâ. Alles, was der Build sehen kann, ist deklariert.Einmal verpackt, lĂ€sst sich dieselbe Derivation ĂŒberall zusammensetzen: als devShell-Paket, innerhalb eines Container-Images, auf einer NixOS-Maschine oder als Remote-Build via nix.buildMachines.
dockerToolsWenn Ihr Deployment Container nutzt, ersetzt Nix Ihre Dockerfiles:
# buildLayeredImage â each package becomes its own layer
dockerTools.buildLayeredImage {
name = "myapp";
tag = "latest";
contents = [ pkgs.bash pkgs.coreutils myapp ];
config = {
Cmd = [ "${myapp}/bin/myapp" ];
Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
ExposedPorts = { "8080/tcp" = {}; };
};
created = "now"; # or a fixed timestamp for full reproducibility
}
nix build .#container
docker load < result
Was Sie anstelle eines Dockerfiles erhalten:
buildLayeredImage).sed-Kette eines Dockerfiles.genAttrs ĂŒber ["x86_64-linux" "aarch64-linux"] leitet beide Images ab; extendModules leitet Umgebungsvarianten aus einer Basis ab. Keine pro-Architektur-Dockerfiles, die auseinanderdriften.checks, und nix flake check baut sie in der CI, bevor irgendetwas eine Registry erreicht.In einem Air-Gapped-Cluster rentiert sich dies sofort: Images werden einmal gebaut, in eine lokale Registry geladen, und die Nodes benötigen ĂŒberhaupt kein Docker Hub. nix build â docker load â docker tag â push ist die gesamte Publish-Pipeline.
NixOS ist dasselbe Modell, angewandt auf die gesamte Maschine: /etc wird aus einer deklarativen Konfiguration generiert, jede Ănderung ist eine neue Generation, und ein Rollback ist ein Eintrag im Boot-MenĂŒ.
# configuration.nix (or a flake's nixosConfigurations)
{ config, pkgs, ... }: {
boot.loader.systemd-boot.enable = true;
networking.hostName = "web-01";
services.nginx = {
enable = true;
virtualHosts."example.com" = {
root = "/var/www";
forceSSL = true;
enableACME = true;
};
};
users.users.admin = {
isNormalUser = true;
extraGroups = [ "wheel" ];
};
system.stateVersion = "24.11";
}
nixos-rebuild switch # apply and activate
nixos-rebuild boot # activate on next boot
nixos-rebuild switch --rollback # back to the previous generation
Den operativen Unterschied kann man kaum ĂŒberbewerten: âGestern hat es noch funktioniertâ wird zur Auswahl im Boot-MenĂŒ, nicht zur Wiederherstellung aus einem Backup. Jedes nixos-rebuild switch erstellt eine neue Generation; der Bootloader behĂ€lt die letzten N. Ein missglĂŒcktes Upgrade kostet einen Neustart.
FĂŒr Flotten deployt Colmena (oder comin fĂŒr den GitOps-Ansatz) viele Maschinen aus einem Flake:
# hive.nix
{
meta = { nixpkgs = import nixpkgs { system = "x86_64-linux"; }; };
defaults = { pkgs, ... }: {
environment.systemPackages = [ pkgs.vim pkgs.wget ];
};
node-a = { name, ... }: {
networking.hostName = name;
deployment.targetHost = "node-a.example.com";
deployment.buildOnTarget = false; # lokal bauen, Closure kopieren
};
}
colmena apply # alle Knoten, parallel
colmena apply --on node-b # Subset basierend auf Tags
Every node building nixpkgs from source is a waste of the universe. A self-hosted cache serves pre-built store paths; post-build-hook uploads every new path automatically.
| Tool | Storage | Signing | Fit |
|---|---|---|---|
| nix-serve | local store | on-the-fly | simplest, single machine |
| Attic | S3-compatible (dedup) | server-side | air-gap, multi-tenant, LRU GC |
| Harmonia | local store | on-the-fly | Rust, zstd, built-in TLS |
For the air-gapped reference cluster, Attic is the recommendation: its S3 backend can sit on the existing Ceph cluster (RGW), global dedup saves scarce bandwidth, and server-side signing means builders never hold the signing key. One services.atticd module on a cache node turns cluster rebuilds from a day into minutes.
The Nix store is world-readable. Secrets therefore never enter it:
.age files committed to git, decrypted at activation. The simplicity winner.LoadCredential= â the runtime complement: the unit receives the secret as a file under /run/credentials, no Nix involvement at all.# agenix
age.secrets.database-password = {
file = ./secrets/database-password.age;
owner = "mariadb";
mode = "0400";
};
diskodisko declares partitions, filesystems, and LUKS in the same flake as the OS; nixos-anywhere installs a remote machine from it over SSH:
disko.devices.disk.main = {
device = "/dev/sda";
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = { size = "500M"; type = "EF00"; content = { type = "filesystem"; format = "vfat"; mountpoint = "/boot"; }; };
root = { size = "100%"; content = { type = "filesystem"; format = "ext4"; mountpoint = "/"; }; };
};
};
};
nixos-anywhere --flake .#node-a root@node-a # Provisionierung, von Null auf NixOS
impermanence wipes the root filesystem on every boot; only explicitly declared state survives:
fileSystems."/" = { device = "none"; fsType = "tmpfs"; options = [ "defaults" "size=25%" "mode=755" ]; };
fileSystems."/persistent" = { device = "/dev/vg/root"; neededForBoot = true; fsType = "btrfs"; options = [ "subvol=persistent" ]; };
A compromised or corrupted machine returns to its declared state by rebooting. Combined with lanzaboote (UEFI Secure Boot) and A/B OTA updates, the machine stops being a machine and becomes an appliance.
testers.runNixOSTestThe ultimate reproducibility payoff: integration tests boot the exact configuration you will deploy, in QEMU VMs, and assert service-to-service behavior:
checks.integration = pkgs.testers.runNixOSTest {
nodes.machine = { ... }; # die zu testende NixOS-Konfiguration
testScript = ''
machine.wait_for_unit("nginx.service")
machine.wait_for_open_port(80)
machine.succeed("curl -f http://localhost/")
'';
};
nix flake check fĂŒhrt dann aus: eval checks, Image-Builds und vollstĂ€ndige VM-Integrationstests â derselbe Ausdrucksbaum, den die CI testet, ist der Baum, der ausgeliefert wird.
flake.lock.formatter-Output â Formatierung ist ein Build-Schritt, kein Diskussionspunkt.checks in jedem Flake â CI-Gates via nix flake check.dockerTools-Images mit Patches als AusdrĂŒcke, Multi-Arch via genAttrs.post-build-hook.LoadCredential= fĂŒr Runtime-Secrets.runNixOSTest fĂŒr alles, was in der Produktion nicht kaputtgehen darf.Starten Sie heute mit nix-shell -p git jq, committen Sie morgen Ihr erstes shell.nix, und lassen Sie die Store-Pfade ab dann die Diskussion ĂŒber Reproduzierbarkeit fĂŒhren.