
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.
Checke `.hoody/crontab` ins Repo neben deine Jobs ein. Wenn das Deploy-Skript einen Container für `main`, `feature/billing-v2` oder einen beliebigen Preview-Branch hochfährt, PUTet es diese Datei an die Cron-API des neuen Containers. Der Zeitplan reist mit dem Branch — und verschwindet, wenn der Branch verschwindet.
eine Datei pro Branch · gleicher Pfad in jedem Repo · kein gemeinsamer Cron-Server
Jeder Branch-Container betreibt Hoody Cron. Das Deploy-Skript liest die eingecheckte crontab und PUTet sie an den Raw-crontab-Endpoint des neuen Containers. Der Container fährt den Zeitplan, den die Datei beschreibt — nicht mehr, nicht weniger.
#!/bin/sh
# Provisioniere einen frischen Container für diesen Branch.
BRANCH=$(git branch --show-current)
CTR=$(hoody containers create --from main-snapshot)
# Ersetze die crontab des Containers durch die aus dem Repo.
curl -X PUT --data-binary @.hoody/crontab \
-H "Content-Type: text/plain" \
https://$CTR-cron-1.hoody.com/users/root/crontab
# Fertig. Der Zeitplan des Branches lebt in seinem Container.
echo "deployed $BRANCH → $CTR"# Hoody Cron Raw-crontab-Endpoint — ersetzt die ganze Datei atomar.
PUT /users/root/crontab HTTP/1.1
Host: ctr_4d72b9-cron-1.hoody.com
Content-Type: text/plain
0 2 * * * /srv/jobs/billing-rollup-v2.sh
*/15 * * * * /srv/jobs/sync-stripe.py
@hourly curl -fsS http://localhost/healthz
*/5 * * * * /srv/jobs/diff-v1-v2.sh
HTTP/1.1 200 OK
# 200 OK: Cron-Daemon lädt neu, Zeitplan in unter einer Sekunde aktiv.Die crontab sind Daten, die der Branch ausliefert, nicht State, den der Cron-Server sich merkt. Lösche den Container, und es bleibt kein Eintrag zum Aufräumen — die Datei ging mit der Disk.
Sobald der Zeitplan eine Datei im Repo ist, verschwinden drei Arten von Arbeit.
Wenn du `billing-rollup.sh` auf v2 änderst, landet der neue Zeitplan im selben Pull Request. Der Reviewer sieht die Cron-Zeile direkt neben dem Skript. Revert einen Commit, und der Zeitplan kehrt mit zurück.
Branch-Container sind ephemer. Wenn du den Branch mergst oder schließt, fährst du den Container ab. Die crontab lebte in ihm, also verschwindet der Zeitplan ohne Janitor — kein gemeinsamer Cron-Server hält veraltete Einträge.
Ein stündlicher Experimentaljob auf `experiment/llm-rollups` läuft in seinem eigenen Container mit eigenem Dateisystem. Stagings Cron-Daemon sieht ihn nie; Productions Cron-Daemon sieht ihn nie. Es gibt keine `if BRANCH_ENV`-Wachen in den Jobs selbst.
Das Standardmodell „eine ops-verwaltete crontab" und das Branch-gebundene Modell scheitern in entgegengesetzte Richtungen. Gleicher Job, sehr unterschiedlicher Blast Radius.
Der Unterschied ist kein Feature — es ist, wo der Zeitplan lebt. Eine Datei, die der Branch trägt, vs. eine Zeile in einer geteilten Tabelle, von der sich der Branch borgt.
Per-Container-Cron ist eine echte REST-Oberfläche — drei Endpoint-Familien, Standard-Cron-Syntax, volle Per-User-Isolation. Zahlen aus der Cron-API-Spec, keine erfundenen Benchmarks.
Jeder Branch-Container hat seine eigene Per-User-crontab. PUTe die ganze Datei, GETe sie zurück, ersetze sie atomar. Keine geteilte Schedule-Tabelle im Hintergrund.
Raw-crontab (GET/PUT), verwaltete Einträge (POST/PATCH/DELETE mit UUIDs und `expires_at`) und Per-User-Listing. Pick, was dein Deploy-Skript braucht.
Standard `min hour day month dow` plus Makros: `@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly`. Gleiche Syntax, die deine `.hoody/crontab` schon nutzt.
Laut Hoody Cron API: GET/PUT /users/[user]/crontab und POST/PATCH/DELETE /users/[user]/entries an der Cron-Service-URL jedes Containers.
Der Zeitplan lebt neben dem Code, der auf ihm läuft, im selben Container, auf demselben Branch.
Sechs Orte, an denen der Cron-Zeitplan früher lebte, keiner davon neben dem Code. Die Branch-gebundene crontab macht sie alle überflüssig.
Hör auf, Zeitpläne über Systeme hinweg zu synchronisieren. Checke die crontab ein. Lass den Branch sie tragen.