Skip to content
Free and open source on GitHub

Seed More Than Your Database

Describe your application state once, in reusable HCL components. Orchard builds it with the tools you already have, from databases to APIs to scripts, and tears it down when you’re done. One definition for your demos, tests, and local development.

scenarios/checkout.hcl

component "merchant" "store" {
  source   = "../components/merchant.hcl"
  for_each = { eu = "Acme Europe", us = "Acme America" }
  inputs   = { name = each.value, region = each.key }
}

action "http_request" "subscribe" {
  method = "POST"
  url    = "/subscriptions"
  body   = jsonencode({
    merchant_id = component.merchant.store["eu"].id
  })
}

action "exec_run" "reindex" {
  command = "./scripts/reindex.sh"
  args    = ["${component.merchant.store["eu"].id}"]
}

Terminal

orchard plan scenarios/checkout.hcl
orchard run  scenarios/checkout.hcl

Built for

How It Fits Together

component

Reusable units of state

A component is like a function: typed inputs, outputs, and the actions that create its state. It also says how to undo them.

Build a library of components (a merchant, a subscription, a user with SSO) and every scenario can reuse them.

component "merchant" {
  variable "name"   { type = string }
  variable "region" {
    type    = string
    default = "us"
  }

  action "postgres_query" "create" {
    query = <<-SQL
      INSERT INTO merchants (name, region)
      VALUES ('${var.name}', '${var.region}')
      RETURNING id, name
    SQL

    lifecycle {
      teardown {
        query = "DELETE FROM merchants WHERE id = ${self.id}"
      }
    }
  }

  output "id"   { value = action.postgres_query.create.id }
  output "name" { value = action.postgres_query.create.name }
}

scenario

One scenario per application state

A scenario names the providers it talks to, the components it uses, and what it hands back.

It’s more than database fixtures. Mix in API calls and scripts, reference one step’s output in another, and Orchard works out the order. for_each and count turn one block into many.

scenario "checkout" {
  required_providers {
    postgres = { source = "builtin/postgres" }
    http     = { source = "builtin/http" }
    exec     = { source = "builtin/exec" }
  }

  variable "dsn"     { default = env("DATABASE_URL") }
  variable "api_url" { default = env("API_URL") }

  provider "postgres" { dsn = var.dsn }
  provider "http"     { base_url = var.api_url }

  component "merchant" "store" {
    source   = "../components/merchant.hcl"
    for_each = { eu = "Acme Europe", us = "Acme America" }
    inputs   = { name = each.value, region = each.key }
  }

  action "http_request" "subscribe" {
    method = "POST"
    url    = "/subscriptions"
    body   = jsonencode({
      merchant_id = component.merchant.store["eu"].id
    })
  }

  action "exec_run" "reindex" {
    command = "./scripts/reindex.sh"
    args    = ["${component.merchant.store["eu"].id}"]
  }
}

orchard plan

See it before it happens

Plan type-checks every reference, resolves the dependency graph, and prints what would run, without touching any external system.

Run it in CI to catch a broken scenario before anyone needs it.

$ orchard plan scenarios/checkout.hcl
▸ checkout
  4 actions, 2 components — via exec, http, postgres

component "merchant" "store"["eu"]
  source  ../components/merchant.hcl
  inputs  name, region
  ↳ action "postgres_query" "create"  via postgres

component "merchant" "store"["us"]
  source  ../components/merchant.hcl
  inputs  name, region
  ↳ action "postgres_query" "create"  via postgres

  action "exec_run" "reindex"  via exec
    also after  component.merchant.store["eu"]
  action "http_request" "subscribe"  via http
    also after  component.merchant.store["eu"]

✓ Plan is valid

orchard run · orchard teardown

Build it, then clear it away

Run builds the state and writes a record of everything it did. Hand that record to teardown and Orchard undoes each step in reverse dependency order, so foreign keys unwind cleanly.

$ orchard run scenarios/checkout.hcl
record: orchard_checkout_run_20261023T103045Z_a3f29e5b.json

$ orchard teardown scenarios/checkout.hcl --latest
tearing down run 20261023T103045Z_a3f29e5b (orchard_checkout_run_20261023T103045Z_a3f29e5b.json)

Components

Reusable units of state with typed inputs and outputs, instead of copied seed scripts.

Automatic wiring

Reference one step's output in another and Orchard builds the dependency graph.

Plan before run

Type-check references and preview the graph without side effects, locally or in CI.

Teardown

Every run writes a record. Teardown replays it in reverse and keeps going if one step fails.

Repetition

for_each and count turn one block into many: a merchant per region, three failed payments.

Pluggable providers

Postgres, MySQL, SQLite, Redis, S3, gRPC, HTTP, and exec built in. Any binary that speaks the JSON-over-stdio protocol can be a provider.

Install

One binary, no daemon or server. It reads HCL files and talks to the systems you point it at.

Write your first scenario

brew install pthm/tap/orchard