YAML & CLI
Manage SteadyCron jobs from a YAML manifest using the CLI — install, authenticate, export, validate, plan, and apply.
Instead of clicking through the dashboard, you can declare your jobs in a YAML manifest, commit it to your repo, and use the CLI to reconcile your account to match. This gives you PR-based reviews, a full change history, and the ability to manage multiple environments without click-ops.
If you’d rather use HCL, the Terraform provider manages the same resources inside your existing infrastructure stack.
Prerequisites
Install the CLI
Option 1 — .NET global tool (recommended)
Requires the .NET 10 runtime.
dotnet tool install -g steadycron
steadycron --version
Update later with dotnet tool update -g steadycron.
Option 2 — self-contained binary
No runtime required. Download the single-file binary from the Releases page:
# 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/
Binaries for Linux arm64, macOS, and Windows are on the same Releases page.
Authenticate
Create an API key under Settings → API keys in the dashboard. Mutating commands
(apply, sync) require a full-access key; export, validate, and plan work
with a read-only key.
Set the key in your environment:
export STEADYCRON_API_KEY=sc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Or save it to the config file so it persists across shell sessions:
steadycron config set --api-key sc_...
Verify connectivity:
steadycron config show --check
Getting your first manifest
Export existing jobs (recommended)
If you already have jobs in the dashboard, export converts your entire account
state into a manifest file so you can start managing it as code without losing
anything:
steadycron export --namespace production > manifests/production.yaml
The --namespace flag assigns all exported resources to this namespace. Once you
start using apply --prune, only resources inside this namespace will be touched —
jobs you create later via the dashboard land in the default namespace and are never
deleted.
The exported file is safe to commit immediately: API keys, webhook URLs, and other
secrets are replaced with ${VAR_NAME} placeholders — you supply the real values
through environment variables at apply time. Template variables ({{var}}) are
preserved as-is.
After export:
- Review the file and check that each resource has an
idfield (see Stable identity). - Validate the manifest:
steadycron validate manifests/production.yaml. - Commit it. The manifest is now the source of truth — future changes should go through the file, not the dashboard.
Starting from scratch
If you’re setting up fresh, init generates a fully commented boilerplate that covers every
available field — channels, tags, variables, HTTP jobs, heartbeat monitors, and alert rules — with
an inline explanation for each one:
steadycron init -o steadycron.yaml
No API key is needed. Open the file, delete the sections you don’t use, fill in your values, then validate and apply:
steadycron validate steadycron.yaml
steadycron plan steadycron.yaml
steadycron apply steadycron.yaml
Pass --terraform to generate the equivalent Terraform HCL boilerplate instead:
steadycron init --terraform -o main.tf
Alternatively, here’s a minimal manifest you can write by hand:
namespace: production
channels:
- id: ops-slack
name: Slack #ops
kind: slack
config:
webhook_url: ${SLACK_WEBHOOK_URL}
jobs:
- id: weekly-digest
name: Weekly digest email
kind: http
method: POST
url: https://api.myapp.com/jobs/digest
schedule: "0 9 * * 1"
timezone: Europe/Berlin
timeout: 120
retries: 3
rules:
- channel: ops-slack
trigger: on_failure
- id: nightly-backup
name: Nightly DB backup
kind: heartbeat
schedule: "0 2 * * *"
grace: 1800
rules:
- channel: ops-slack
trigger: on_missed_heartbeat
See the Manifest reference for the full field list.
The workflow
| Command | What it does |
|---|---|
steadycron export | Dump current account state to a manifest |
steadycron validate | Check a manifest for schema errors |
steadycron plan | Preview what would change — no writes |
steadycron sync | Apply changes (create + update; no deletes) |
steadycron apply | Apply changes; add --prune to also delete |
validate
Check a manifest for schema errors before applying:
steadycron validate manifests/production.yaml
Exits 0 on success, 1 with a list of errors on failure. Wire this into CI as a
pre-flight check on every PR.
plan
plan shows you exactly what would change — in a format like terraform plan —
without writing anything:
steadycron plan manifests/production.yaml
Example output:
~ weekly-digest update retries: 2 → 3
+ invoice-reminder create kind: http schedule: 0 17 * * 5
- old-report (not in manifest — would be deleted with --prune)
1 to create · 1 to update · 1 pending deletion
Use plan in your PR pipeline to comment the diff on every pull request. See
CI/CD setup for the GitHub Action that does this automatically.
sync / apply
sync creates new jobs and updates changed ones. It does not delete jobs that
exist in your account but are absent from the manifest:
steadycron sync manifests/production.yaml
To also remove unmanaged jobs (those in the namespace but not in the file), use
apply --prune:
steadycron apply --prune manifests/production.yaml
apply is transactional: if any operation fails, the rest of the batch is not
applied and the error is reported with the specific resource that caused it.
Dry-run: pass --dry-run to any sync or apply call to print what would
happen without making changes — equivalent to plan but useful when scripting:
steadycron apply --prune --dry-run manifests/production.yaml
Namespaces {#namespaces}
A namespace scopes which resources the CLI manages. Without a namespace, the
CLI operates on the default namespace and cannot safely use --prune (it would
delete any job not in the manifest, including jobs you created via the dashboard).
Name namespaces after environments or teams:
# manifests/production.yaml
namespace: production
jobs: [...]
# manifests/staging.yaml
namespace: staging
jobs: [...]
The dashboard shows the namespace each job belongs to. Resources created via the
dashboard (no namespace) sit in the default namespace and are never touched by a
namespaced apply --prune.
For a monorepo with multiple environments, keep one manifest file per namespace:
manifests/
production.yaml # namespace: production
staging.yaml # namespace: staging
Apply each independently:
steadycron apply --prune manifests/production.yaml
steadycron apply --prune manifests/staging.yaml
Stable identity {#stable-identity}
Every resource in a manifest has an id field. This is the stable internal key
— it never changes, even if you rename the job’s name.
jobs:
- id: nightly-backup # ← stable forever
name: Nightly database backup (prod) # ← free to rename
kind: heartbeat
schedule: "0 2 * * *"
Why this matters for heartbeats: each heartbeat check has a unique ping URL tied to its internal ID. When you rename a heartbeat job, the ping URL is preserved — you do not need to update your scripts, CI pipelines, or monitoring integrations.
If two entries share the same id, validate will reject the manifest.
Secrets & variables {#secrets}
Secrets must never appear literally in the manifest. SteadyCron supports two mechanisms:
${ENV_VAR_NAME} — CLI substitution
The CLI reads these from the process environment at apply time and substitutes them before sending the manifest to the API. Works in any field.
channels:
- id: ops-slack
kind: slack
config:
webhook_url: ${SLACK_WEBHOOK_URL} # ← CLI reads from env at apply time
jobs:
- id: invoice-job
kind: http
url: https://${API_HOST}/jobs/invoices
headers:
Authorization: Bearer ${INVOICE_API_KEY}
In CI, set these as repository secrets and pass them to the job:
# GitHub Actions
env:
STEADYCRON_API_KEY: ${{ secrets.STEADYCRON_API_KEY }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
INVOICE_API_KEY: ${{ secrets.INVOICE_API_KEY }}
{{template_var}} — server-side substitution
Template variables are resolved by the SteadyCron server at execution time, not at
apply time. They work only in HTTP job fields (url, headers, body) and are
useful for long-lived secrets you never want in your shell history or CI logs.
jobs:
- id: data-export
kind: http
url: https://api.myapp.com/export
headers:
Authorization: Bearer {{api_token}}
Manage variable values in the dashboard under Settings → Variables.
steadycron export emits both kinds of placeholder automatically — the exported
manifest is safe to commit without manual scrubbing.
${ENV_VAR_NAME} | {{template_var}} | |
|---|---|---|
| Resolved by | CLI at apply time | Server at execution time |
| Works in | Any field | HTTP job url, headers, body only |
| Use for | API keys, webhook URLs, env-specific values | Long-lived secrets, per-execution values |
See Template variables for the full reference.
Next steps
- CI/CD setup — post plan diffs on PRs and apply on merge with GitHub Actions.
- Terraform provider — if your team already runs Terraform, manage SteadyCron resources in HCL alongside the rest of your infrastructure.
- Manifest reference — every top-level key and field, with the complete annotated example.
- Activity & Logbook — monitor your account with
steadycron report(activity overview) andsteadycron logbook(full event history).