steadycron 1.4.0
dotnet tool install --global steadycron --version 1.4.0
dotnet new tool-manifest
dotnet tool install --local steadycron --version 1.4.0
#tool dotnet:?package=steadycron&version=1.4.0
nuke :add-package steadycron --version 1.4.0
SteadyCron CLI
The official command-line interface for SteadyCron — schedule, run, and monitor cron jobs as code. Declare your entire account — jobs, alert channels, tags, variables — in a YAML manifest, commit it to your repo, and reconcile with a single command:
steadycron sync steadycron.yaml --namespace prod
Install
As a .NET global tool (recommended)
Requires the .NET 10 runtime.
dotnet tool install -g steadycron
steadycron --version
Update with dotnet tool update -g steadycron.
Self-contained binary
Download the single-file binary for your platform from the Releases page — no .NET runtime required:
# example: Linux x64
curl -Lo steadycron https://github.com/steadycron/cli/releases/latest/download/steadycron-linux-x64
chmod +x steadycron && sudo mv steadycron /usr/local/bin/
Authenticate
Create an API key in the dashboard under Settings → API keys, then provide it via an environment variable:
export STEADYCRON_API_KEY=sc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Configuration is resolved in this order (first wins):
--api-key/--api-urlflagsSTEADYCRON_API_KEY/STEADYCRON_API_URLenvironment variables- The config file (
steadycron config set --api-key sc_...) - Built-in defaults (
--api-urldefaults tohttps://api.steadycron.com)
steadycron config show --check # verify connectivity
A read-only key can run
export,validate, and read-only sub-commands. Mutating commands (apply,sync,jobs create/pause/delete, etc.) require a full key.
The v2 manifest
A manifest declares your whole account: channels, tags, variables, and jobs. The server reconciles from this single source of truth.
# examples/steadycron.yaml
version: 2
namespace: prod # required for --prune
channels:
- id: slack-oncall
name: Slack #oncall
kind: slack
config:
webhook_url: ${SLACK_WEBHOOK_URL} # CLI env-var substitution
tags:
- id: env-prod
key: env
value: prod
variables:
- id: digest-token
name: digest_token # used in HTTP fields as {{digest_token}}
value: ${DIGEST_TOKEN} # value resolved at load time, never committed
jobs:
- id: weekly-digest # stable id — rename the job without re-creating it
name: weekly-digest-email
kind: http
method: POST
url: ${API_BASE_URL}/jobs/digest
schedule: "0 9 * * 1" # Mondays at 09:00
timezone: Europe/Berlin
timeout: 120
retries: 3
headers:
Authorization: "Bearer {{digest_token}}" # server-side template substitution
tags: ["env:prod"]
rules:
- channel: slack-oncall
trigger: on_failure
severity: p1
- id: nightly-db-backup
name: nightly-db-backup
kind: heartbeat
schedule: "0 2 * * *"
grace: 1800
See examples/steadycron.yaml for the complete field reference.
Two interpolation mechanisms
| Syntax | Where it runs | Scope |
|---|---|---|
${ENV_VAR} and ${ENV_VAR:-default} |
CLI, at load time | Any manifest field |
{{template_var}} |
Server, at execution time | HTTP job URL / headers / body only |
The CLI resolves ${...} before sending the manifest to the API. {{...}} is passed through
untouched and substituted by the server when the job fires.
Secrets and .env files
Secret fields never leave the server in plaintext: on export they come back as ${SC_…}
placeholders (alert-channel credentials such as webhook_url/bot_token/secret/webhook headers,
and template-variable values). To apply such a manifest you must supply those values.
Provide them with one or more --env-file flags (repeatable; values take precedence over the
process environment so a file prepared for the target account is authoritative):
steadycron apply production.yaml --namespace prod --env-file secrets.env
When a manifest references any required ${...} placeholder, apply/sync/plan refuse to run
without an --env-file — pass one, or --allow-process-env to source the values from the current
environment instead (e.g. CI that injects secrets as env vars). validate supports --env-file but
never enforces this (it's a local, read-only lint).
Restoring to another account
export + apply move a whole account — jobs, channels, tags, variable values, rules — to a
fresh one over the CLI, no UI required:
# 1. On the source account: export the manifest and a scaffold of the secrets it needs.
steadycron export -o production.yaml --write-env secrets.env
# 2. Fill in secrets.env with the real values (it lists every ${SC_…} the manifest references).
# 3. On the target account (different API key): apply, sourcing the secrets from the file.
steadycron apply production.yaml --namespace prod --prune --env-file secrets.env
The server recreates every resource and sets channel credentials and variable values from the
placeholders your .env resolves. (Variable-value round-trip requires a server that supports it;
older servers export variable names only.)
v1 manifests (deprecated)
Version 1 (jobs-only, name-keyed, no namespace/channels/tags/variables) is still accepted. The CLI prints a deprecation warning and recommends upgrading:
⚠ Manifest version 1 is deprecated. Run 'steadycron export' to upgrade to v2.
Support will be removed no earlier than two minor releases after this notice.
Workflow: validate → plan → apply
validate — lint locally, no API call
steadycron validate steadycron.yaml
steadycron validate ./manifests/
Checks schema, cron syntax, cross-references (job tags → declared tags, rule channels → declared channels), duplicate IDs, and kind-specific field constraints. Fast CI gate — runs in milliseconds. Exits 0 on success, 2 on errors.
plan — preview what would change
steadycron plan steadycron.yaml --namespace prod
steadycron plan ./manifests/ --namespace prod --output json # machine-readable server plan
steadycron plan steadycron.yaml --namespace prod --detailed-exitcode
Calls the server's /api/reconcile dry-run and renders its authoritative plan. The server is the
single source of truth — the CLI never computes its own diff.
--detailed-exitcode exits 2 when drift is detected (Terraform-style), 0 when clean.
Without the flag, any plan exits 0 unless there are errors.
sync — plan + apply
# Interactive: shows plan, prompts to confirm, then applies
steadycron sync steadycron.yaml --namespace prod
# Non-interactive (CI): applies without prompt
steadycron sync steadycron.yaml --namespace prod --yes
# Include --prune to delete server resources removed from the manifest
steadycron sync ./manifests/ --namespace prod --prune --yes
sync is declarative: it creates new resources, updates changed ones, and (with --prune)
deletes ones missing from the manifest. Without --prune, orphaned server resources are reported
but not deleted.
apply — alias for sync --yes
steadycron apply ./manifests/ --namespace prod --prune
Applies immediately without prompting. Typical use: CI pipelines on merge to the default branch.
export — pull the current account state as a manifest
steadycron export -o steadycron.yaml # whole account → file
steadycron export -o steadycron.yaml --write-env secrets.env # + a .env scaffold for its secrets
steadycron export --scope jobs -o jobs.yaml # jobs only
steadycron export --scope job weekly-digest-email # single job → stdout
steadycron export --format json # JSON instead of YAML
Writes the manifest verbatim from the server. Secret fields — alert-channel credentials
(webhook_url, bot_token, secret, webhook headers) and template-variable values — are
replaced with ${SC_…} placeholders, never plaintext. The CLI prints a summary of the required
environment variables to stderr (so piping with -o stays clean), and the manifest itself carries a
# required env vars: header block.
--write-env <path> writes a ready-to-fill .env scaffold listing every referenced secret
(refuses to overwrite an existing file). Fill it in, then apply … --env-file <path>.
Useful for bootstrapping: export your current account, commit the result, and manage it as code going forward. To move an account to a new one, see Restoring to another account.
Multi-file manifests
Separate concerns across files or directories:
steadycron validate ./manifests/
steadycron plan ./manifests/ --namespace prod
steadycron apply manifests/channels.yaml manifests/jobs.yaml --namespace prod
When multiple files are used, they must agree on version and namespace. Duplicate resource id
values across files are an error.
Cron as Code in CI
Add the SteadyCron GitHub Action to plan on pull requests and apply on merge:
# .github/workflows/steadycron.yml
name: SteadyCron
on:
pull_request:
paths: ["steadycron/**"]
push:
branches: [main]
paths: ["steadycron/**"]
permissions:
contents: read
pull-requests: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./action # or: steadycron/sync-action@v1
with:
manifest-path: steadycron/
namespace: prod
prune: "true"
api-key: ${{ secrets.STEADYCRON_API_KEY }}
mode: auto # plan on PRs, apply on push to main
The action:
- Installs the CLI (pinned version).
- On
pull_request— runssteadycron plan --output json, formats the plan as Markdown, and posts/updates a sticky PR comment (find-and-replace so re-runs update in place). Fails the check if the plan has errors (limit violations, conflicts, etc.). - On
pushto the default branch — runssteadycron apply --yes.
See examples/ci/ for standalone pull_request and push workflow files.
Managing resources directly
steadycron jobs list # table of all jobs
steadycron jobs list --kind heartbeat --status missed
steadycron jobs get weekly-digest-email # by name or id
steadycron jobs logs warm-cdn-cache -n 20
steadycron jobs pause weekly-digest-email
steadycron jobs resume weekly-digest-email
steadycron jobs run warm-cdn-cache
steadycron jobs delete old-job --yes
steadycron jobs create --name warm-cache --url https://api.myapp.com/warm \
--method GET --interval 900 --skip-if-running
steadycron cron preview "*/15 9-17 * * 1-5" --timezone Europe/Berlin
steadycron tags list
steadycron tags create env prod --color green
steadycron vars list
steadycron vars set digest_token "sk_live_…"
steadycron channels list
steadycron channels create --name "Ops email" --kind email --to ops@example.com
steadycron rules list nightly-db-backup
steadycron rules add nightly-db-backup \
--channel "Ops email" --trigger missed_heartbeat --severity p1
Add --json to any command for machine-readable output.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success (or plan with no drift when --detailed-exitcode is not set) |
1 |
Unexpected error |
2 |
Manifest load/validation error; also plan --detailed-exitcode when drift is detected |
3 |
API error |
4 |
Missing/invalid credentials (401/403) |
5 |
Plan/apply has errors[] (limit violations, conflicts) or per-resource failures |
130 |
Cancelled (Ctrl+C) |
--detailed-exitcodeoverloads code2forplan: exit2= drift detected, exit0= clean. This matchesterraform planbehaviour and is documented as an opt-in so that CI scripts branching on exit code2for validation errors are unaffected.
Development
dotnet build
dotnet test
dotnet run --project src/SteadyCron.Cli -- jobs list
Built on .NET 10 with Spectre.Console and YamlDotNet.
License
MIT © SteadyCron.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
See the changelog: https://github.com/steadycron/cli/blob/main/CHANGELOG.md