CLI Reference
The cronlord binary is the whole product. This page catalogues every subcommand, flag, and environment variable it honours, so you can script against it or drop it into an ExecStart= line with confidence.
Global flags
-c, --config PATH path to cronlord.toml (default: ./cronlord.toml)
-h, --help show help
-V, --version print version and exit
Unknown top-level flags are forwarded to the subcommand. Subcommand flags like --schedule on job add don’t conflict.
Global environment
| Variable | Default | Purpose |
|---|---|---|
CRONLORD_HOST | 127.0.0.1 | Listen host |
CRONLORD_PORT | 7070 | Listen port |
CRONLORD_DATA | ./var | Data directory (db + logs live here) |
CRONLORD_DB | $DATA/cronlord.db | SQLite path override |
CRONLORD_LOG_DIR | $DATA/logs | Per-run log directory |
CRONLORD_ADMIN_TOKEN | unset | Bearer token gating /api/* |
CRONLORD_LOG_TTL_DAYS | 30 | Run log retention; 0 disables auto-rotation |
CRONLORD_BLOCK_PRIVATE_NETS | unset | 1 refuses RFC1918, loopback, link-local, CGNAT, multicast HTTP targets |
CRONLORD_CLAUDE_CLI | claude | Override Claude Code CLI binary for kind = "claude" jobs |
Env > cronlord.toml > built-in default.
cronlord serve / cronlord server
Starts the scheduler + HTTP server + background reapers. This is the everyday command.
./cronlord server
CRONLORD_ADMIN_TOKEN=$(openssl rand -hex 32) ./cronlord server
What runs in the process:
- Scheduler - tickless loop, wakes for the next due job.
- HTTP server - Kemal on
$CRONLORD_HOST:$CRONLORD_PORT. - Log reaper - deletes run logs older than
CRONLORD_LOG_TTL_DAYSonce per day. Skipped whenCRONLORD_LOG_TTL_DAYS=0. - Lease reaper - re-queues runs whose worker lease expired (every 30 s).
SIGINT / SIGTERM flushes the scheduler, closes the DB, and exits 0.
cronlord migrate
Applies pending SQL migrations from db/migrations/. serve runs migrations automatically on boot, so this is for manual maintenance (e.g. running migrations against a snapshot before swapping binaries).
./cronlord migrate
# => ok
cronlord doctor [--json]
Probes the install in under a second and reports what’s wrong. Pair with docs/troubleshooting.md — anything doctor flags has a fix in that guide.
./cronlord doctor
# [ ok ] binary cronlord 0.3.6 (crystal 1.19.1)
# [ ok ] db var/cronlord.db integrity_check=ok journal_mode=wal
# [warn] admin_token unset - OK while bound to 127.0.0.1, required before exposing publicly
# ...
# summary: 11 ok, 1 warn, 0 fail
Checks covered:
| Check | What it verifies |
|---|---|
binary | Version + Crystal runtime |
config | cronlord.toml present or defaults in use |
data_dir | Exists and is writable |
db | File exists, integrity_check=ok, reports journal_mode |
migrations | All on-disk migrations applied; lists pending |
log_dir | Size, retention policy, warns over 1 GiB |
stuck_runs | Counts runs in running older than 24h |
workers | Registered workers heartbeating within 5m |
timezone | IANA tzdata available (America/New_York loads) |
admin_token | Set for non-loopback binds, ≥32 chars |
private_nets_guard | CRONLORD_BLOCK_PRIVATE_NETS state |
claude_cli | claude on PATH when kind='claude' jobs exist |
Exit codes:
0— all checks ok1— at least one warning, no failures2— one or more failures (use in CI:cronlord doctor || exit 1)
--json emits the same report as a stable JSON object ({version, checks: [{name, status, detail}]}) suitable for monitoring pipelines.
cronlord job <subcommand>
Non-interactive CRUD for jobs. Respects CRONLORD_ADMIN_TOKEN only at the HTTP surface - the CLI talks to the DB directly.
job list
./cronlord job list
Columns: ID, NAME, SCHEDULE, ON, COMMAND (truncated at 60).
job add
./cronlord job add \
--schedule '*/5 * * * *' \
--command 'ping -c1 example.com' \
--name heartbeat
| Flag | Required | Notes |
|---|---|---|
--schedule | yes | Cron expression or @hourly/@daily/@weekly/@monthly. Parsed before write. |
--command | yes | Shell snippet (for --kind=shell), URL/JSON (http), or prompt (claude). |
--name | no | Display name; defaults to the id. |
--id | no | Stable id; defaults to UUID. Useful for idempotent upserts. |
--timeout | no | Seconds until SIGTERM -> SIGKILL. 0 = unlimited. |
--kind | no | shell (default), http, claude. |
Prints the job id on success. Exits 2 with a message on bad input.
job rm <id>
./cronlord job rm 3a4f...
# => deleted
# or => not_found
Cascades to runs via ON DELETE CASCADE in the schema.
job run <id>
Triggers a run immediately (trigger = "cli"), blocks until it finishes, prints the run id. Useful for manual smoke tests or invoking a job from another cron.
./cronlord job run nightly-backup
cronlord runs [--job ID] [--limit N]
Show recent runs. Defaults to 20 rows newest first. Filter to one job with --job=<id>.
./cronlord runs --limit 5
./cronlord runs --job heartbeat --limit 50
cronlord worker <subcommand>
Manages the remote worker registry. All subcommands except run talk to the local DB.
worker register <name> [--label L]...
Creates a new worker row with a random plaintext secret. The plaintext is printed once; there is no way to recover it. Copy it, then derive the HMAC key on the worker host with sha256(plaintext_secret).
./cronlord worker register runner-1 --label linux --label gpu
# id: b1d7abd0-...
# name: runner-1
# secret (shown once - copy it now):
# 47caaaeb19...
Labels are how job.labels restrict eligibility. A job with labels = ["linux"] is only leased by workers advertising linux. Empty job.labels means “any worker”.
worker list
./cronlord worker list
# b1d7abd0-... runner-1 on 2026-04-19 11:42:01
# 5e02c1aa-... runner-2 off never
Columns: id, name, enabled, last_seen (unix -> local time).
worker rm <id>
./cronlord worker rm b1d7abd0-...
# => deleted
Removes the worker row and detaches any in-flight leases (runs flip back to queued via the lease reaper).
worker run
Runs the reference worker polling loop. Expected environment (all required, flags override each one-to-one):
| Env / flag | Purpose |
|---|---|
CRONLORD_URL / --url | Scheduler base URL, e.g. https://cron.example.com |
CRONLORD_WORKER_ID / --id | Worker id from worker register |
CRONLORD_HMAC_KEY / --key | sha256(plaintext_secret) |
CRONLORD_WORKER_NAME / --name | Display name (logs only; default: hostname) |
CRONLORD_LEASE_SEC / --lease | Lease window, default 60 |
CRONLORD_POLL_SEC / --poll | Idle poll interval, default 5 |
export CRONLORD_URL=https://cron.example.com
export CRONLORD_WORKER_ID=b1d7...
export CRONLORD_HMAC_KEY=$(printf '%s' "$PLAIN_SECRET" | openssl dgst -sha256 | awk '{print $2}')
./cronlord worker run --name "$(hostname)"
SIGINT / SIGTERM drains the current run (if any) and exits 0.
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Domain failure (not found, unknown subcommand, …) |
2 | Bad invocation (missing required flag, invalid cron) |
See also
- Getting Started - zero to first job.
- API - the REST surface, same CRUD as the CLI.
- Deployment - systemd, Docker, reverse proxies.
- Troubleshooting - when something is wrong.