
Sechzig Container auf einem Server
Eine Bare-Metal-Box führt Dutzende bis Hunderte von Hoody-Containern aus. KSM und BTRFS-Dedup machen die Marginalkosten nahezu null.
Deine Matrix-CI fächert sich über dreißig Test-Runner auf. Jeder davon braucht dasselbe 800-MB-Image. Stream das Tarball mit ?n=30 in einen Pipe-Pfad. Alle dreißig Worker rufen dieselbe URL per curl ab. Die Bytes laufen einmal durch, der Server hält nichts, und keine Registry-Credentials werden rotiert.
# stream the image oncetar c ./image.tar | curl -T - https://pipe.hoody.com/build-12af?n=30EIN SENDER LINKS. DREISSIG GET-EMPFÄNGER RECHTS. DIE PIPE WARTET, BIS ALLE LAUSCHEN, DANN LAUFEN DIE BYTES EINMAL HINDURCH.
Die Pipe ist ein Fan-out-Router ohne Disk. Der POST des Senders auf /api/v1/pipe/[path]?n=30 blockiert, bis dreißig Empfänger sich mit demselben n auf derselben URL verbinden. Dann fließen die Bytes vom Build-Container direkt zu jedem Runner — gleichzeitig, in der Geschwindigkeit des langsamsten Empfängers.
tar c | curl -T - https://pipe/.../build?n=30Der Build-Container pipet das Tarball direkt in curl. Keine Datei geschrieben, keine Registry gepusht.
POST /api/v1/pipe/[path]?n=30Der Server hält den Sender, bis alle dreißig Empfänger verbinden. Abweichendes n liefert 400. Vorab verbundene Empfänger sind okay.
curl https://pipe/.../build?n=30 | tar xJeder Runner bekommt identische Bytes. Backpressure kommt vom langsamsten Empfänger, nicht von der Bandbreite des Senders.
Nichts persistiert. Nichts wird gecached. Die Pipe vermittelt die Verbindung und tritt dann zurück. Wenn der langsamste Runner fertig ist, ist der Transfer fertig — und die URL ist weg.
Naiv: dreißig Registry-Pulls desselben 800-MB-Tarballs, dreißig kalte Caches, dreißig Netzwerk-Round-Trips. Pipe: ein Egress, ein Transfer, der langsamste Empfänger gibt das Tempo vor.
12s
Ein Egress mit Linerate. Der langsamste Empfänger bestimmt das Tempo, aber niemand lädt erneut.
1× / Build
Bytes verlassen den Builder einmal, fächern sich an der Pipe auf. Keine S3-GET-Gebühren, keine Docker-Hub-Pulls.
0 Bytes
Die Pipe hält nichts auf Disk. Keine Registry zum Aufräumen, kein Cache-Key zum Invalidieren.
Die Wall-Time-Zahl geht von einer 30-fachen Matrix im selben regionalen Netz wie der Build-Container aus; Cross-Region-Transfers hängen an der Inter-Region-Bandbreite, nicht an der Pipe.
Sobald der Build eine URL und dreißig curls ist, fällt ein ganzer Stack an CI-Gerüst weg. Kein Artefakt-Storage zum Altern. Keine Registry-Credentials zum Rotieren. Keine Cache-Action zum Debuggen.
Backpressure ist in die Pipe eingebaut. Die schnellen Worker verschwenden keinen Registry-Round-Trip, um auf den langsamen zu warten — sie warten an der Pipe und trinken dann im selben Tempo. Niemand lädt erneut.
Nichts wird in eine Registry gepusht, also muss sich auch nichts daran authentifizieren. Die URL selbst ist das Credential — kurzlebig, auf einen Transfer beschränkt, evictet, wenn der Build fertig ist.
Bytes verlassen den Builder einmal. Die Pipe broadcastet. Du zahlst einen Egress pro Build statt dreißig Registry-Pulls pro Matrix-Run.
Die Pipe ist pro Build, nicht pro Key. Es gibt keinen GitHub-Actions-Cache, der danebenliegt, kein Buildx-Layer-Mysterium, kein abgestandenes Tarball vom letzten Main.
Dasselbe Muster handhabt node_modules, .pnpm-store, target/, den Wheel-Cache, den Dataset-Shard. Was streamt, fächert sich auf.
Ein Sender. Dreißig Empfänger. Null S3-Rechnungen.
Ein 30-fach-Push, der neunzig Sekunden und einen S3-Hit gekostet hat, dauert zwölf Sekunden und einen einzigen Egress. Niemand lädt erneut. Keine Registry-Credentials werden rotiert. Die URL evictet sich selbst, wenn die Matrix fertig ist.
Die Teile, die ein Matrix-CI-Flow normalerweise zusammensetzen muss — Registry, Cache-Action, Mirror, Custom-Upload-Schritt. Die Pipe faltet sie in eine URL.
Hör auf, dasselbe Tarball dreißig Mal zu pushen. Push es einmal. Lass dreißig curls den Stream teilen.