Save products you love by clicking the heart icon.
Ein CI-Skript, das jedes Image in deinen Compose-Dateien prüft, schlägt plötzlich bei einem Tool fehl – und alle Tags des Tools sollen 'nicht existieren', weil sie auf quay.io und nicht auf Docker Hub liegen. So validierst du Image-Tags über Registries hinweg mit anonymen Bearer-Tokens und HEAD-Manifesten.
Nicht jeder Terraform-Drift ist gleich. Ein praxisnahes Playbook: Drift kontinuierlich in der CI erkennen, nach Risikostufe klassifizieren und gezielt beheben, ohne im Alarmrausch zu untergehen.
Im Internet ist ein Binary Cache eine Optimierung. Hinter einer Firewall ist er der Unterschied zwischen einem Deploy in zehn Minuten und einem, der die Welt aus dem Quellcode kompiliert.
Nix' Superkraft — hermetische, reproduzierbare Builds — hat einen Preis: Der erste Build von nixpkgs auf einer Maschine dauert Stunden, weil jede Derivation isoliert aus dem Quellcode gebaut wird. Normalerweise fängt das offizielle cache.nixos.org das ab. In einem luftabgeschotteten Cluster gibt es kein „normal": Knoten hinter der Firewall substituieren entweder aus einem Cache, den man selbst betreibt — oder jeder Knoten baut alles neu, bei jedem Deploy. Dieses Playbook ist der Weg, den wir für einen luftabgeschotteten SCS-Kubernetes-Cluster (K3s auf NixOS, HTTP-Proxy unter proxy.internal:3128, Ceph-RGW-Objektspeicher) gegangen sind: Server wählen, signieren, Upload automatisieren, jeden Knoten anbinden.
Nix-Substitution ist ein inhaltsadressierter (content-addressed) Download über HTTP. Das Store-Protokoll ist einfach: ein nix-cache-info-Endpunkt (StoreDir, WantMassQuery, Priority) und eine .narinfo-Datei pro Store-Pfad mit Hash, Referenzen und — entscheidend — einer Sig:-Zeile. Diese Signatur ist das gesamte Sicherheitsmodell:
trusted-public-keys der Clients, nicht im Netzwerk.Der Plan besteht also aus vier Teilen: ein Server hinter der Firewall (die Signierung bleibt serverseitig — Attic's Managed Signing), ein automatischer Upload-Pfad (damit nie jemand manuell pusht), eine Client-Konfiguration auf jedem Knoten und jeder Build-Maschine sowie der öffentliche Schlüssel, den diese Konfiguration in trusted-public-keys festlegt.
| Tool | Mandantenfähig | Backend | Dedup | Fazit |
|---|---|---|---|---|
| Attic | Ja (Teams + Tokens) | S3-kompatibel (Ceph RGW, MinIO) | Global | Standard für eine Flotte |
| Harmonia | Nein | Bedient /nix/store direkt | Pro Pfad | Schnell, single-tenant, zstd + TLS eingebaut |
| nix-serve | Nein | Bedient direkt | Nein | Minimaler Fallback, Signierung on-the-fly |
| Cachix | Gehostet | Cloud | Ja | Nur mit Egress sowieso |
Attic gewinnt bei einer Multi-Node-Flotte: Signierung serverseitig (ein Schlüssel, nie auf den Clients), globale Deduplizierung über den gesamten Store, Team-Tokens für Mandanten (CI, Laptops, Knoten) und ein S3-kompatibles Backend, das direkt auf das ohnehin vorhandene Ceph RGW abbildet. nix-serve steht aus gutem Grund an zweiter Stelle: Wer genau eine Maschine und einen Signaturschlüssel hat, kommt mit einem 10-Zeilen-Modul aus. Alles Folgende setzt Attic voraus.
Ein minimales NixOS-Modul — das JWT-Secret und die S3-Zugangsdaten liegen in einer per agenix bereitgestellten Environment-Datei, der Store in S3 (das Modul kommt aus dem Attic-Flake: attic.nixosModules.atticd):
# Cache-Host (NixOS)
{ config, pkgs, ... }:
{
age.secrets.atticd-env = {
file = ../secrets/atticd-env.age; # ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64 + S3-Zugangsdaten
owner = "atticd";
};
services.atticd = {
enable = true;
environmentFile = config.age.secrets.atticd-env.path; # vom Modul gefordert
settings = {
listen = "[::]:8080";
storage = {
type = "s3"; # Ceph RGW — globale Dedup über Teams
region = "us-east-1";
bucket = "nix-cache";
endpoint = "https://ceph-rgw.internal";
credentials = {
access_key_id = "attic";
secret_access_key = "CHANGE_ME"; # aus dem Age-Secret — nie ein Literal im Store
};
};
chunking.nar-size-threshold = 64 * 1024; # 64 KiB: NARs ab dieser Größe chunkieren
garbage-collection = {
interval = "12 hours";
default-retention-period = "750 days";
};
};
};
}
Zwei Details, die später wehtun:
garbage-collection muss gesetzt sein. Ohne sie behält Attic jeden Pfad für immer und der S3-Bucket wächst durch den CI-Betrieb unbegrenzt weiter. 12 hours / 750 days heißt: verwaiste Pfade zweimal täglich abräumen, alles älter als zwei Jahre verwerfen, GC-Roots erhalten.atticadm make-token. Wer pusht (CI, Laptop-Builder), hält ein kurzlebiges Token; alle anderen lesen nur — genau dafür sind trusted-public-keys da.Nichts sollte von Hand hochgeladen werden. Zwei funktionierende Mechanismen: der Nix-native post-build-hook (ein Skript, das Nix nach jedem erfolgreichen Build ausführt) oder der watch-store-Daemon von Attic. Zuerst der Hook:
# /etc/nix/upload-to-cache.sh — läuft nach jedem Build
#!/bin/sh
set -eu; set -f; export IFS=' '
exec attic push internal:default $OUT_PATHS
# als post-build-hook eingebunden — jeder Build pusht automatisch:
nix.settings.post-build-hook = "/etc/nix/upload-to-cache.sh";
Für Dauerläufer (CI-Runner, dedizierte Builder) ist der attic watch-store-Daemon die sauberere Standardlösung: Er beobachtet den lokalen Store und pusht jeden neuen Pfad — kein Hook-Skript zu pflegen. Jetzt lädt ein nixos-rebuild auf Knoten A jeden frischen Pfad hoch; die Knoten B, C und D substituieren ihn binnen Minuten. Ein post-build-hook oder watch-store auf jedem Produzenten stellt sicher, dass jeder Pfad, der irgendwo entsteht, überall verfügbar ist.
Jeder NixOS-Knoten und jede Build-Maschine bekommen dieselben zwei Einstellungen — den Substituter (woher holen) und den öffentlichen Schlüssel (wem vertrauen):
nix.settings = {
substituters = [
"http://cache.internal:8080"
"https://cache.nixos.org/" # als Fallback für die wenigen Maschinen mit Egress
];
trusted-public-keys = [
"cache.internal:...." # via: attic cache info <cache> → „Public Key:"
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
];
};
Die gesamte Kette prüft man auf einem Knoten mit einem Befehl:
nixos-rebuild dry-build --flake .#mySystem
# "these N derivations will be built" ← Cache-Miss, schlechtes Zeichen
# "these N paths will be fetched (S bytes)" ← Treffer, genau das will man
Fällt ein Knoten aufs Bauen zurück, fehlt entweder der Pfad im Cache, der Schlüssel stimmt nicht oder die Priorität ist falsch konfiguriert — die Dry-Run-Ausgabe sagt einem das, bevor der Knoten zwei Stunden damit verbringt, Chromium zu kompilieren.
Der Cache-Host liegt in der Luftabschottung — woher kommt also sein erster Store? Der Trick: Luftabschottung ist meist eine Netzwerk-Lücke, keine totale Isolation. Man hat einen HTTP-Proxy (proxy.internal:3128), der auf einen Controller-Knoten mit Egress zeigt. Bootstrap in drei Schritten:
nixos-rebuild mit gesetzten Proxy-Env-Variablen ausführen — der Proxy lädt von cache.nixos.org herunter und füllt den lokalen Store.attic push in den neuen Cache laden (oder das bereits verdrahtete post-build-hook machen lassen).substituters der Flotte auf den internen Endpunkt umstellen; Egress bleibt auf den Knoten geschlossen, NO_PROXY=...cache.internal hält ihren Traffic vom Proxy fern.Danach kann der Egress jedes Knotens außer der Proxy-Ausnahme auf null sinken — eine wirklich schöne Sicherheitseigenschaft für einen luftabgeschotteten Cluster: Knoten sprechen mit einer Handvoll Endpunkten, und fast alle gehören einem selbst.
environmentFile) — nie ein Literal in /nix/store; Signierung bleibt serverseitig (Managed Signing)trusted-public-keys versioniert auf jedem Knoten (das ist die eigentliche Vertrauensgrenze)NO_PROXY, damit Cache-Traffic nie durch den Proxy läuftnixos-rebuild dry-build in der CI überwachen; ein stiller Substitutions-Einbruch ist eine Build-Zeit-Regression, bevor er ein Brand istEin signierter, deduplizierter Binary Cache ist das stille Rückgrat einer luftabgeschotteten Flotte. Er macht Builds nicht schneller — er macht sie gemeinsam, und genau dieses Teilen verwandelt „jeder Knoten baut die Welt neu" in „eine Maschine baut, die Flotte substituiert". Einmal eingerichtet, amortisiert sich der Cache schon beim nächsten Deploy.