Ein CI-Broker für Radicle, der wirklich baut — die Reise durch jede Wand

Vor zwei Wochen stand der Plan: ein CI-Broker, der auf Patch-Events meiner Radicle-Seed-Node hört, in-cluster baut und signiert. Das Diagramm war sauber. Die Realität war ein wochenlanger Kampf mit cib-Entrypoints, Flux, das meine Shell-Variablen auffraß, stale :latest-Tags, rootless buildah, das in Kubernetes prinzipiell nicht geht, und einem binfmt_misc-Handler, der auf Kernel 6.x immer wieder verschwand. Hier ist jede Wand, gegen die ich gelaufen bin — und wie der Broker am Ende doch baut.

In Radicle CI — eine Pipeline, die auf Patches reagiert habe ich den Plan beschrieben: Meine Seed-Node läuft in-cluster und führt einen Event-Stream — jede neue Patch-Ref, jedes COB-Update wird sichtbar. Ein CI-Broker abonniert diesen Stream, übersetzt ein Patch-Event in einen Build und schreibt das Ergebnis zurück auf den Patch. Sauberes Diagramm, klare Idee, ein offenes Trust-Problem, das ich für lösbar hielt.

Was dieser Beitrag erzählt, ist die Lücke zwischen dem Diagramm und einem grünen Häkchen. Sie war größer, als mir lieb war. Zwischen „der Broker ist deployt" und „der Broker baut wirklich ein signiertes Image" lagen rund zwei Dutzend Commits, die fast alle fix: heißen — und jeder davon steht für eine Wand, gegen die ich gelaufen bin. Kein Architektur-Pitch, sondern ein Reisebericht durch Entrypoints, die nicht stimmen, durch Flux, das meine Shell-Variablen frisst, durch rootless buildah, das in Kubernetes prinzipiell nicht funktioniert, und durch einen Kernel, der meinen QEMU-Handler immer wieder vergaß.

Radicle CI — eine Pipeline, die auf Patches reagiert

Der Code des Labs liegt in einer P2P-Forge, die CI aber noch nicht: Builds laufen post-merge auf einer externen .build.yml. Wie ein Radicle-CI-Broker das schließt — er hört auf die Patch-Events der eigenen Seed-Node, baut und signiert in-cluster und schreibt das Ergebnis zurück auf den Patch. Plus das eigentlich harte Problem: Vertrauen in einer Forge ohne Owner.
Stand: Design/Roadmap. Issue 7f834f9 : „Wire Radicle CI once the lab repo is published (patches already wired)." Die Seed-Node läuft in-cluster, der Patch-Workflow steht — der CI-Broker ist das letzte fehlende Stück. Dieser Beitrag beschreibt, wie er andocken soll.

In Von SourceHut zu Radicle ist der Lab-Code in eine Peer-to-Peer-Forge umgezogen: keine zentrale Instanz mehr, Patches und Issues leben als signierte Collaborative Objects (COBs), Flux deployt vom kanonischen main. Ein Faden blieb dort offen — und genau um den geht es hier: Radicle ist eine Forge, kein CI-System. Der Code liegt P2P, der Build aber noch nicht.

Das OCI-Format und der Wechsel von Harbor zu Zot

Warum OCI mehr ist als nur Docker-Images, weshalb mein Build daemonlos mit Skopeo und Crane statt docker push arbeitet — und wie Zot mein schwergewichtiges Harbor abgelöst hat

„Container-Image“ ist im Sprachgebrauch fast zum Synonym für „Docker“ geworden — dabei ist das Format längst von Docker entkoppelt und in der Open Container Initiative (OCI) standardisiert. Dieser Beitrag erklärt, was das konkret bringt, warum meine Build-Pipeline deshalb mit Skopeo und Crane statt docker push arbeitet, und wie ich meine bisherige Harbor-Registry gegen das deutlich schlankere Zot getauscht habe.

Signierte Images erzwingen: Cosign in der Pipeline, Kyverno im Cluster

Wie selbstgebaute OCI-Images in der CI mit cosign signiert und im Cluster von Kyverno-Policies im Enforce-Modus verifiziert werden

Wer eigene Container-Images baut und in einer eigenen Registry hält, hat ein Vertrauensproblem in petto: Woher weiß der Cluster, dass ein Image wirklich aus meiner Pipeline stammt und nicht unterwegs manipuliert wurde? Meine Antwort ist eine geschlossene Kette aus zwei Hälften — cosign signiert in der CI, Kyverno verifiziert im Cluster und lässt nichts Unsigniertes durch.