IaC workflow
How to adopt, validate, preview, and apply a SteadyCron YAML manifest — from first export to CI/CD automation.
The SteadyCron CLI turns your account into a text file you can version, review in PRs, and deploy from CI. This page covers every command in the workflow.
Prerequisites {#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, jobs create, etc.) 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
Commands at a glance
| 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 |
Step 1 — export (adopting an existing account)
If you already have jobs in the dashboard, export round-trips them into a manifest so
you can start managing them as code without losing anything:
export STEADYCRON_API_KEY=sc_...
steadycron export --namespace production > manifests/production.yaml
The exported file is safe to commit. Any API keys, webhook URLs, or other secrets are
replaced with ${VAR_NAME} placeholders — you supply the real values through environment
variables at apply time. Template variables ({{var}}) in HTTP job fields are preserved
as-is.
Review the file, add id values to any jobs that are missing them, and commit.
Step 2 — 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.
Step 3 — 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 this in your PR pipeline to comment the plan diff on every pull request. See CI/CD setup for the GitHub Action that does this automatically.
Step 4 — 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 & ownership {#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.
Multiple namespaces in a monorepo
Organise one manifest file per namespace:
manifests/
production.yaml # namespace: production
staging.yaml # namespace: staging
workers.yaml # namespace: workers-team
Apply each independently:
steadycron apply --prune manifests/production.yaml
steadycron apply --prune manifests/staging.yaml
Stable identity and rename safety
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 have the same id, validate will reject the manifest.
Secrets & variables {#secrets}
Secrets must never appear literally in the manifest. SteadyCron provides two mechanisms:
1. ${ENV_VAR_NAME} — CLI environment 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
name: Slack #ops
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 }}
2. {{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
values that vary per execution or are computed server-side.
jobs:
- id: data-export
kind: http
url: https://api.myapp.com/export/{{date}}
body: '{"run_id": "{{uuid}}"}'
steadycron export emits both kinds of placeholder automatically — the exported manifest
is safe to commit without manual scrubbing.