Cron as code,
end to end

Author every job, monitor, channel, and alert rule in YAML or Terraform. Review changes in pull requests, apply them from GitHub Actions, and watch the run from inside your app with the .NET and Python SDKs — all from one repository.

Free tier — no credit card required.

  1. 1

    Author

    Declare jobs, monitors, channels & rules.

    YAMLTerraform
  2. 2

    Reconcile

    validate → plan → apply. No drift.

    CLIProvider
  3. 3

    Ship

    Plan on every PR, apply on merge.

    GitHub Actions
  4. 4

    Observe

    Watch the run from inside your app.

    .NETPython

Pick your dialect.
Same result.

One file declares your whole account — HTTP jobs, heartbeat monitors, alert channels, rules, tags, and variables. Write it as a YAML manifest or as Terraform HCL; both express the same resources and reconcile the same way.

Use the manifest if you want a single self-contained file. Reach for Terraform if cron should live alongside the rest of your infrastructure. The parity is the point — neither is a second-class citizen.

# steadycron.yaml — your whole account as code
version: 2
namespace: prod

channels:
  - id: ops-slack
    kind: slack
    config:
      webhook_url: ${SLACK_WEBHOOK_URL}

jobs:
  - id: weekly-digest
    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-db-backup
    kind: heartbeat
    schedule: "0 2 * * *"
    grace: 1800

Terminal

$ steadycron validate steadycron.yaml
 2 jobs · 1 channel · 1 rule — no errors

$ steadycron plan steadycron.yaml --namespace prod
  ~ weekly-digest      update     retries: 2 → 3
  + nightly-db-backup  create     heartbeat · 0 2 * * *
  1 to update · 1 to create

$ steadycron sync steadycron.yaml --namespace prod
 applied — account now matches the manifest

validate → plan → apply. No surprises.

The steadycron CLI reconciles your account to match the manifest. plan shows exactly what would change, sync applies it transactionally, and --prune deletes what you removed — scoped to a namespace so the blast radius is always explicit.

  • export adopts an existing account into a manifest in seconds.
  • import crontab / import vercel migrate existing schedules.
  • Using Terraform? terraform plan / apply / import drive the exact same loop.
CLI & IaC workflow

Plan on every PR. Apply on merge.

The steadycron/action@v1 GitHub Action posts a sticky plan comment on each pull request — reviewers see exactly which jobs, monitors, channels, and rules a change introduces — then applies on merge. Your main branch is the source of truth.

The same action drives both tools: set tool: yaml for manifests or tool: terraform for HCL.

CI/CD setup
steadycron bot commented on this PR

Plan: 1 to update · 1 to create · 1 to delete

~ weekly-digest      update   retries: 2 → 3
+ invoice-reminder   create   http · 0 17 * * 5
- old-report         delete   (removed from manifest)
# .github/workflows/cron.yml
- uses: steadycron/action@v1
  with:
    command: plan          # 'apply' on merge to main
    manifest: steadycron.yaml
    comment-on-pr: "true"
  env:
    STEADYCRON_API_KEY: ${{ secrets.STEADYCRON_API_KEY }}

The job and the code that watches it — same key, same repo.

Declare a heartbeat monitor with a stable key in YAML or Terraform. Reference that exact key from your application with the .NET or Python SDK. SteadyCron then knows when the job started, succeeded, failed, or never ran.

The SDKs never create monitors — they resolve the key with a read-only API key, and pings are fire-and-forget: a monitoring outage never takes down your job. Schedule, alert rules, and instrumentation all live as code, together.

In your manifest / HCL

key: nightly-db-backup

import steadycron

@steadycron.job("nightly-db-backup")   # same key
def backup():
    run_backup()   # start · success · fail — automatic

Why SteadyCron for cron as code

The whole loop as code

Schedule, monitoring, alert routing, and the in-app instrumentation — one repository, one review, one deploy.

EU-hosted, GDPR-native

Runs on Hetzner in Germany, governed by German law. No US sub-processors for cron execution or job data.

Tools you already use

YAML or Terraform, a dedicated CLI, a GitHub Action, and first-party SDKs — no bespoke DSL, no lock-in to a console.

Common questions

YAML manifest or Terraform — which should I use?

Whichever your team already standardises on. The YAML manifest with the steadycron CLI is self-contained and needs no other tooling; the Terraform provider fits naturally if you already run Terraform. Both express the same resources and share the same plan / apply / import loop. Manage any given resource with one tool, not both.

Can I use this in my CI/CD pipeline?

Yes — that's the recommended workflow. The steadycron/action@v1 GitHub Action runs plan on pull requests (posting a sticky comment) and apply on merge — for both YAML (tool: yaml) and Terraform (tool: terraform). See the CI/CD setup guide.

Do the .NET and Python SDKs create the monitors?

No. The SDKs only send pings — they resolve a monitor by its stable key using a read-only API key. Declare the heartbeat monitor in YAML or Terraform first, then reference that same key from your application code.

Are my API keys or secrets ever stored as code?

No. In the manifest, ${ENV_VAR} placeholders are substituted by the CLI at apply time and {{template_var}} values are resolved server-side at execution time — steadycron export emits both automatically. In Terraform, secrets are declared as sensitive variables and supplied at apply time.

Does managing jobs as code replace the dashboard?

No. Namespaces let code-managed jobs and dashboard-created jobs coexist in one account. Only resources inside your manifest's namespace are touched by --prune — everything else is left alone.

What happens to my heartbeat ping URL when I rename a job?

Nothing changes. Each job's ping URL is tied to its stable id / key, not its display name — so a rename is just a rename, and your scripts and SDK references keep working.

Cron jobs that live in your repo

Sign up, export your current account, and commit your first manifest in minutes — in YAML or Terraform.

  • Free tier — no credit card
  • YAML or Terraform — equal support
  • EU-hosted, GDPR-native