Service scaffolding with a catalog
A new service shouldn't take a week of copy-paste and a forgotten catalog entry. Here's a single OrchStep task that scaffolds the repo, registers it in your service catalog, and smoke tests it before anyone touches it.
blog/catalog-scaffoldStanding up a new service at most companies is an archaeology project. You clone the repo someone said was "close enough," delete the parts that don't apply, rename twelve things, guess at the CI config, and — three weeks later, during an incident — discover the service was never registered in the catalog, so nobody knew who owned it.
Scaffolding and registration belong together. A service that exists but isn't in the catalog is a service that doesn't exist when you need it. So make the act of creating one a single workflow that scaffolds, registers, and proves the result responds.
One task, three guarantees
Here's the whole thing: render the service manifest, register it in the catalog, then hit its health endpoint with a retry so a slow cold start doesn't fail the run.
name: catalog-scaffold
defaults:
service: "widget"
tier: backend
tasks:
# `orchstep run new --var service=billing`
new:
steps:
- name: scaffold
func: render
args:
template: |
name: {{ vars.service }}
owner: platform
tier: {{ vars.tier }}
outputs:
manifest: "{{ result.output }}"
- name: register
func: shell
do: echo "registering {{ vars.service }} in the service catalog (tier {{ vars.tier }})"
outputs:
id: "svc-{{ vars.service }}"
- name: smoke
func: shell
do: echo "GET /healthz on {{ steps.register.id }} -> 200"
retry:
max_attempts: 3
interval: "1s"
backoff_rate: 2.0
- name: done
func: shell
do: echo "{{ vars.service }} scaffolded, registered as {{ steps.register.id }}, smoke test passed"Three things now happen together or not at all. The render step produces the catalog manifest from a template — swap args.output_file in and it writes to disk instead of staying in an output. The register step hands back a catalog ID that the smoke step reads from its steps.register.id output, so the health check runs against the thing you just registered, not a hardcoded URL.
The smoke test is the point
Scaffolding that doesn't verify is just a fancier copy-paste. The retry block is what makes the check honest:
- name: smoke
func: shell
do: echo "GET /healthz on {{ steps.register.id }} -> 200"
retry:
max_attempts: 3
interval: "1s"
backoff_rate: 2.0A brand-new service is slow on its first request. Without retry, your scaffolder fails on a cold start and everyone learns to ignore it. With three attempts and a widening interval, a transient slow start passes and a genuinely broken service still fails — which is exactly the signal you want. More patterns: Error Handling.
Wire inputs from a form, or from a prompt
The example takes the service name with --var service=billing, which is what a backstage-style portal or a CI job would pass. If you want the same workflow to be friendly at a terminal, add a prompt step and let it ask:
- name: ask
func: prompt
args:
message: "New service name"
type: text
default: "{{ vars.service }}"
outputs:
name: "{{ result.value }}"Interactively it asks; in a pipeline it falls back to the default and keeps moving. One workflow, both front doors. See the prompt function.
What you actually gained
| Step | Done by hand | One orchstep run new |
|---|---|---|
| Repo skeleton | clone + delete + rename | render from a template |
| Catalog entry | "I'll do it later" (never) | register step, every time |
| Health check | manual curl, maybe | smoke with retry |
| Owner + tier metadata | guessed during an incident | set at creation |
If your org already has a mature internal developer platform that does all this, keep it. But if "new service" today means a senior engineer babysitting a checklist, this collapses the checklist into one command — and the catalog entry stops being optional.
Where to go next
- The render function — turn templates into files and manifests
- The prompt function — interactive input that auto-skips in CI
- Error Handling — retry, catch, finally, timeouts
Spin it up on your own catalog: orchstep run new --var service=billing --dry-run shows the full plan before it registers anything.
curl -fsSL https://orchstep.dev/install.sh | sh