Skip to content
use-cases / byo-cron-per-tenant / hero
BYO cron / per-tenant schedules

Let your customers BYO their own cron schedule.

Your customers type the cron expression. You POST it to their container's crontab. There's no shared queue to fair-share, no minimum interval to enforce, no support ticket about "why my job didn't run on peak Monday."

use-cases / byo-cron-per-tenant / mechanism

The customer types into your UI. You POST to their container.

Your settings page renders an input field. Their tenant container exposes a Cron API. Submit forwards one POST. No global scheduler, no per-tenant filtering logic, no "max 100 jobs across all customers" cap.

your-backend → tenant-container
POST /users/root/entries
// the form submit relays the customer's expression unchanged
POST https://acme-cron.hoody.com/users/root/entries
Content-Type: application/json

{
  "schedule": "0 9 * * 1-5",
  "command": "/jobs/sync_crm.sh",
  "comment": "Sync Salesforce contacts",
  "enabled": true
}
tenant-container → your-backend
201 Created
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "sch_8a3f1c",
  "schedule": "0 9 * * 1-5",
  "next_run": "2026-05-04T09:00:00Z",
  "enabled": true
}

// the schedule is now in this tenant's crontab and nowhere else

The Hoody Cron service runs inside each tenant container with managed entries and per-user isolation. The schedule lives where the work runs.

use-cases / byo-cron-per-tenant / powers

What BYO cron gives you that a shared scheduler can't.

When the schedule lives next to the work, the rules of multi-tenant scheduling change. The constraints are local. The blast radius is local. The features are local.

no shared queue

No fair-queueing tax

There's no global thread pool to fight over. Your most demanding tenant runs every minute and never starves your quiet ones — they're not on the same crontab.

self-service

Customers self-serve, you don't validate

You stop being the gatekeeper of "is */1 * * * * allowed for your tier?" Their container, their cron, their CPU bill. Your support inbox empties out.

container-bound

The schedule ships with the tenant

Snapshot the tenant container, you snapshot the crontab. Roll back, restore, fork — the schedules go with it. No external scheduler state to sync.

use-cases / byo-cron-per-tenant / compare

Shared multi-tenant scheduler vs container-bound cron.

The difference shows up in three places: the customer's experience, your support load, and the engineering surface area.

axisshared schedulerBYO container cron
What the customer can pick
5 minute minimum, weekdays only, no concurrent runstier-gated, fair-share rules, peak-hour deferrals
Any 5-field cron expression their container can handle*/1 * * * * if they want it; their CPU pays for it
Where validation happens
Your code, before persist, against a global queue modelregex + capacity check + tier check + overlap check
The container's cron daemon parses the expressionsyntax-only; capacity is one container's, not the fleet's
Failure blast radius
One tenant's slow job stalls the queue for everyonenoisy-neighbor incidents, alerting on queue depth
One container's cron lags one tenantkernel-level isolation; the rest of the fleet doesn't notice

The shared-scheduler version of this feature is a sea of caveats. The BYO version is a five-field input box.

use-cases / byo-cron-per-tenant / support

What goes away when the cron is per-tenant.

Three numbers that change the day you stop running a global queue. Each maps to a feature you no longer have to write or operate.

  1. validation rules to maintain0

    No more tier-gated minimum interval, no max-jobs-per-tenant, no fair-share knobs. The container is the limit.

  2. POST per schedule create

    Customer types, you forward, the container parses. The settings page submit is a single REST call, not an orchestration.

  3. fields the customer owns5

    minute · hour · day-of-month · month · day-of-week. Plus macros (@hourly, @daily). Standard POSIX, not your DSL.

Numbers reflect the BYO container-bound model — actual cron entries scale with each container's CPU and the customer's plan.

use-cases / byo-cron-per-tenant / punchline

The customer's cron expression is the customer's, not yours to validate against a global queue.

shared schedulerBYO per-tenant cron
beforeif (tier !== 'enterprise' && interval < 5min) reject()validation tax: every customer paid for the worst customer
afterPOST /users/root/entries → 201 Createdthe schedule lives in their container; their CPU, their rules
See the Cron API
use-cases / byo-cron-per-tenant / replaces

What this replaces.

The infrastructure pieces a BYO container-bound cron quietly retires.

  • shared multi-tenancy schedulersno global queue to fair-share
  • Quartz with tenant filteringno tenant_id column on every job row
  • custom cron-as-config UIscustomers type, the container parses
  • vendor-managed Airflowno DAG service for a 5-field expression
  • shared Sidekiq with throttlingno global rate limiter across tenants
  • BullMQ with tenant queuesno Redis to babysit per customer
use-cases / byo-cron-per-tenant / cta

Stop being the gatekeeper of someone else's schedule. Hand them the cron field, hand the work to their container.

Read the docs
use-cases / byo-cron-per-tenant / related

Read the others