
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.
Provisioniere am Tag, an dem das Cleanup beginnt, einen Managed-Cron-Eintrag. Setze expires_at ein paar Tage hinter den letzten erwarteten Lauf. Das Skript schneidet sich nächtlich durch die Arbeit und schickt sich dann selbst per DELETE weg, wenn nichts mehr übrig ist. Keine Kalendererinnerung, keine Zombie-Crontab, kein jährliches Aufräum-die-Aufräumer-Review.
# tail of cleanup-stale-uploads.sh removed=$(find /uploads -mtime +90 -delete -print | wc -l) echo "$removed files removed" # self-aware tail: nothing left → retire if [ -z "$(ls /uploads)" ]; then curl -fsS -X DELETE \ "$CRON_URL/entries/$ENTRY_ID" fi
Der selbstbewusste Schwanz. Der Cron-Eintrag ist die letzte Zeile seines eigenen Skripts.
Tag 1 räumte 247 alte Dateien weg. Tag 7 räumte keine — und feuerte DELETE auf seinen eigenen Eintrag.
# tail of cleanup-stale-uploads.sh removed=$(find /uploads -mtime +90 -delete -print | wc -l) echo "$removed files removed" # self-aware tail: nothing left → retire if [ -z "$(ls /uploads)" ]; then curl -fsS -X DELETE \ "$CRON_URL/entries/$ENTRY_ID" fi
Der selbstbewusste Schwanz. Der Cron-Eintrag ist die letzte Zeile seines eigenen Skripts.
Drei diskrete Phasen. Jeder Übergang ist mechanisch, keiner verlangt, dass ein Mensch sich erinnert. Der Eintrag weiß, wann seine Arbeit getan ist und wann sein Kalenderslot vorbei ist.
Schedule @daily, command zeigt auf ein Slicer-Skript, expires_at ist knapp hinter den letzten erwarteten Lauf gesetzt. Die Deadline steht im Eintrag — nicht in einem Notion-Doc, nicht in einem Slack-Thread.
Jeder Lauf löscht eine Scheibe veralteter Daten, damit die Datenbank nicht überfahren wird. Tag 1 räumt vielleicht 247 Dateien weg; Tag 6 nur noch 1. Das Tempo ist begrenzt durch das, was tatsächlich übrig ist.
Der letzte Block des Skripts prüft, ob das Ziel leer ist. Wenn ja, feuert er DELETE /entries/[self]. Falls das irgendwie nicht klappt, feuert expires_at ein paar Tage später das Sicherheitsnetz.
Zwei unabhängige Trigger — die eigene Prüfung des Skripts und das expires_at der API — laufen auf dasselbe Ergebnis hinaus: eine Crontab-Zeile, die ihren Zweck nicht überlebt.
Hoody Cron ist ein JSON-CRUD-Wrapper um die System-Crontab. POST erstellt den Eintrag; DELETE entfernt ihn; expires_at ist das Sicherheitsnetz. Das Skript, das nächtlich läuft, ist dasjenige, das weiß, wann es fertig ist — also ist es dasjenige, das DELETE aufruft.
# day 0 — provision the cleanup curl -X POST \ https://cron.containers.hoody.com/users/me/entries \ -H "Content-Type: application/json" \ -d '["schedule":"@daily","command":"/srv/jobs/cleanup-stale-uploads.sh","expires_at":"2026-05-05T00:00:00Z"]' # response HTTP/1.1 201 Created { "id":"f3a1", "expires_at":"2026-05-05T00:00:00Z", "enabled":true }
# inside the cron command itself if [ -z "$(ls /uploads)" ]; then curl -X DELETE \ "$CRON_URL/entries/$ENTRY_ID" fi # response HTTP/1.1 204 No Content # entry f3a1 was here. f3a1 deleted itself.
$ENTRY_ID ist die UUID, die der POST zurückgibt — das Skript kann sie aus einer Datei lesen, die die Command-Line des Eintrags übergeben hat, oder zur Laufzeit aus $HOODY_ENTRY_ID. So oder so: Der Cron-Eintrag löscht den Cron-Eintrag.
Es ist nicht das Löschen, was zählt. Es ist, dass sich in drei Monaten niemand mehr erinnern muss, dass das überhaupt existiert.
@daily läuft alle 24 Stunden. Das Skript löscht eine Scheibe veralteter Daten — ein paar tausend Dateien, ein paar tausend Zeilen — und beendet sich. Die Datenbank bleibt ruhig; die Last-Kurve sieht aus, als wäre nichts passiert.
expires_at steht als JSON im Eintrag. Wenn es feuert, wird die Zeile aus der System-Crontab entfernt. Drei Engineers später blättert niemand mehr durch 200 Zeilen und fragt sich, was cleanup-stale-uploads-v3 wohl noch tut.
Das Skript schickt sich selbst per DELETE weg in der Nacht, in der die Arbeit getan ist. Falls ein Bug diesen Pfad überspringt, pensioniert expires_at den Eintrag ein paar Tage später. Zwei unabhängige Mechanismen; einer von beiden wird feuern.
Jeder Managed Entry ist eine JSON-Zeile, die die API in die System-Crontab injiziert. Die Skalierung ist begrenzt durch das, was Cron selbst aufnehmen kann, nicht durch Hoody.
@daily ist der kanonische Cleanup-Rhythmus. Wenn du häufiger durchgehen willst, kannst du 5-Feld-Ausdrücke bis runter auf * * * * * verwenden — Minuten-Auflösung.
Ein ISO-8601-Timestamp am Eintrag. Wenn er abläuft, entfernt die API die Zeile beim nächsten Sweep. Das Cleanup verweilt nie über seine eigene Deadline hinaus.
DELETE /users/[user]/entries/[id] aus dem laufenden Befehl heraus funktioniert, weil der Cron-Daemon seine eigene Crontab nicht sperrt — die API zieht die Änderung beim Sweep sicher rein.
Standard-5-Feld-Cron-Ausdrücke plus Macros (@hourly, @daily, @weekly, @monthly, @yearly). Pro-User-Isolation; jeder System-User bekommt seine eigene Crontab. Die Cron-Seite des Hoody Kits dokumentiert sowohl Managed Entries als auch rohen Crontab-Zugriff, falls du die ältere Form brauchst.
Das Cleanup läuft nächtlich, bis das, was aufgeräumt wird, weg ist.
Überall dort, wo ein Cleanup-Task von selbst verschwinden soll — das sind die Muster, die es ersetzt:
Provisioniere das Cleanup. Setz sein Renten-Datum. Geh weg.