Soixante conteneurs sur un seul serveur
Une seule machine bare-metal fait tourner des dizaines à des centaines de conteneurs Hoody. La déduplication KSM et BTRFS rend le coût marginal quasi nul.
Le premier job de votre pipeline tarball node_modules et l'envoie dans une URL Hoody. Vingt jobs en aval font curl sur la même URL et déballent. Avec ?n=20, le producteur attend que les vingt workers soient connectés, puis stream une seule fois — diffusé à tous. Pas de bucket S3, pas d'action de cache, pas de facture d'egress.
Pas de bibliothèque cliente, pas de daemon, pas de SDK. Le producteur stream son tarball dans PUT /pipe/cache?n=20. Chaque worker le ressort par GET /pipe/cache?n=20. Le pipe ne garde les octets que pendant le transit — jamais sur disque.
tar emballe node_modules ; zstd compresse au vol ; curl PUT les octets directement dans le path du pipe. Pas de fichier temporaire, pas d'étape upload-artifact, pas de credentials de bucket.
Chaque worker de test fait un GET sur la même URL, décompresse, et déballe dans son répertoire de travail. Les workers lents appliquent du backpressure au producteur sans bloquer les plus rapides.
?n=20Un seul paramètre. Producteur et consommateurs s'accordent sur le même n. Le pipe retient l'upload jusqu'à ce qu'exactement ce nombre de receivers soit connecté, puis ouvre les vannes.
Le cache CI a longtemps été une taxe : du stockage que vous écraserez demain, de l'egress chaque fois qu'un worker tire, du temps d'ingénierie sur les bizarreries de l'action cache. Le pipe efface les trois lignes d'un coup.
Pas de bucket S3 parce qu'il n'y a pas de stockage. Le pipe oublie les octets dès la fin du transfert — rien à facturer en GB-mois ou en GB sortants. Le cache cesse d'être une ligne de facture.
Pas de clés en yaml, pas de séparation save-cache / restore-cache, pas de debug pour comprendre pourquoi un cache hit n'a pas eu lieu sur le bon runner. Juste curl. Les deux mêmes lignes tournent sur GitHub Actions, BuildKite, Jenkins, votre laptop, ou un conteneur cron.
Les branches utilisent simplement des paths différents. /pipe/cache/main, /pipe/cache/feat-x, /pipe/cache/PR-742. Rien à invalider. Rien à éviction. Quand la branche meurt, plus personne ne demande son path — c'est tout le cycle de vie.
Sur une vraie charge — node_modules autour de 800 MB, vingt workers de test en parallèle, cent runs CI par jour — l'essentiel de la facture est l'egress, pas le stockage.
20× egress
Chacun des vingt workers tire le cache depuis S3. Vingt téléchargements d'un tarball de 800 MB, c'est 16 GB d'egress par run CI. Le bucket lui-même est la partie facile — l'egress est ce qui s'accumule.
1× transfert
Le producteur stream les 800 MB une seule fois. Le pipe diffuse les octets aux vingt receivers en transit. Un transfert sur le fil, pas de multiplicateur par receiver, pas de facture de stockage.
Chiffres illustratifs pour un cache de monorepo Node typique. Les économies réelles dépendent de la taille du tarball, du fan-out de workers, et du prix d'egress facturé par votre fournisseur. La forme — linéaire vs constante en nombre de workers — ne change pas.
Le cache, c'est du HTTP. Ça l'a toujours été. On ne s'en était pas rendu compte.
Les caches n'ont jamais été une affaire de stockage. C'était comment livrer les mêmes octets à N workers sans tout reconstruire. HTTP fait ça nativement — dès qu'on laisse une URL diffuser à un nombre connu de receivers. Le bucket était un contournement du fan-out qu'on n'avait pas.
Un PUT, n GETs, octets identiques. Le backpressure est par receiver — le worker lent ne ralentit pas les rapides.
PUT /pipe/cache?n=20Le pipe garde les octets pendant le transfert et les oublie à la fin. Rien à éviction, rien à cycler, rien à sauvegarder.
TTL ≤ 5 min · puis évincéChaque branche choisit son propre path. Pas de keyspace partagé, pas de collisions. Le path est à la fois la clé du cache et l'URL.
/pipe/cache/[branch]La plupart des caches CI résolvent le même problème : livrer le même tarball à N workers. Ils le font par stockage et egress. Le pipe le fait par le fil.
Deux commandes curl. Une URL. Vingt workers nourris.