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

CommandWhat it does
steadycron exportDump current account state to a manifest
steadycron validateCheck a manifest for schema errors
steadycron planPreview what would change — no writes
steadycron syncApply changes (create + update; no deletes)
steadycron applyApply 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.