
1 つのサーバーで 60 のコンテナ
1 つのベアメタルボックスで数十から数百の Hoody コンテナを実行。KSM と BTRFS のデデュプでマージナルコストはほぼゼロ。
クリーンアップを始めた日にマネージド cron エントリをプロビジョンします。expires_at は最後の予定実行を少し過ぎた日に設定。スクリプトは夜ごとに作業を切り分けて進め、何も残らなくなったら自分自身を DELETE します。カレンダーリマインダーも、ゾンビ crontab も、年に一度の「クリーンアップのクリーンアップ」レビューも要りません。
# 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
自己認識する末尾。cron エントリは、自身のスクリプトの最後の 1 行です。
1 日目に古いファイル 247 件をクリア。7 日目はゼロ件 — そして自分のエントリに DELETE を撃ちました。
# 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
自己認識する末尾。cron エントリは、自身のスクリプトの最後の 1 行です。
3 つの離散的なフェーズ。それぞれの遷移は機械的で、人間が思い出す必要はありません。エントリは仕事がいつ終わるか、自分のカレンダー枠がいつ切れるかを知っています。
スケジュールは @daily、コマンドはスライサー スクリプト、expires_at は最後の予定実行の少し後に設定。締め切りはエントリの中にあります — Notion ドキュメントにも、Slack スレッドにもありません。
毎回の実行で古いデータの一部を削除し、データベースを叩き潰さないようにします。1 日目は 247 件、6 日目は 1 件かもしれません。ペースは実際に残っている量で決まります。
スクリプトの最後のブロックは、対象が空かを確認します。空なら DELETE /entries/[self] を撃ちます。万が一それが起きなくても、expires_at が数日後に安全網を発動します。
2 つの独立したトリガー — スクリプト自身のチェックと API の expires_at — が同じ結果に収束します: 目的より長く生き残らない crontab 行。
Hoody Cron はシステム crontab を JSON-CRUD でラップしたものです。POST がエントリを作成し、DELETE が削除し、expires_at が安全網です。夜ごとに走るスクリプトこそ、いつ終わったかを知っている張本人 — だから DELETE を呼ぶのもそのスクリプトです。
# 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 は POST が返した UUID — スクリプトはエントリのコマンドラインで渡されたファイルから読むこともできるし、実行時に $HOODY_ENTRY_ID から取ることもできます。いずれにしても、cron エントリが cron エントリを削除します。
重要なのは削除そのものではありません。3 か月後に誰もこれの存在を覚えていなくていい、ということです。
@daily は 24 時間ごとに走ります。スクリプトは古いデータのスライス — ファイル数千、行数千 — を削除して終了します。データベースは静かなまま、負荷曲線はまるで何も起きていないようです。
expires_at は JSON としてエントリに含まれます。発火するとシステム crontab から行が消えます。3 代後のエンジニアが 200 行を遡って cleanup-stale-uploads-v3 が今も何をしているのか首をひねる、ということはありません。
スクリプトは仕事が終わった夜に自身を DELETE します。バグでそのパスが飛ばされても、expires_at が数日後にエントリを引退させます。独立した 2 つの仕組みのうち、どちらかは必ず発火します。
各マネージドエントリは API がシステム crontab に注入する JSON の 1 行です。スケールは Hoody ではなく cron 自身が保持できる量で決まります。
@daily が定番のクリーンアップ リズム。もっと頻繁に走らせたければ、5 フィールド式で * * * * * — 分単位の解像度まで降りられます。
エントリ上の ISO-8601 タイムスタンプ。期限を過ぎると、API は次のスイープでその行を削除します。クリーンアップは自分の締め切りを越えて居座りません。
実行中のコマンドの内側から DELETE /users/[user]/entries/[id] が動くのは、cron デーモンが自分の crontab をロックしないからです。API が変更を安全にスイープします。
標準の 5 フィールド cron 式とマクロ(@hourly、@daily、@weekly、@monthly、@yearly)。ユーザー単位の分離。各システムユーザーが自分の crontab を持ちます。Hoody Kit の Cron ページは、マネージドエントリと、必要なら従来形の生 crontab アクセスの両方をドキュメント化しています。
クリーンアップは、片付ける対象が消えるまで毎晩走る。
クリーンアップタスクが自然に消え去ることを期待される場面で、これらのパターンを置き換えます。
クリーンアップをプロビジョンし、引退日を設定し、立ち去りましょう。