Scaffold a new service in one command
Stop copy-pasting last quarter's service and find-replacing the name. One OrchStep task prompts for a name and renders every file from templates — interactive locally, fully scriptable in CI.
blog/scaffold-serviceEvery team has a "reference service" that new services get cloned from. The ritual is always the same: copy the directory, find-and-replace old-service with new-service, delete the bits that don't apply, and forget to update two files so the new service ships with someone else's metadata. It works until it doesn't, and it's nobody's job to fix.
Scaffolding is a solved problem — you just need a template engine and a place to ask "what's it called?". OrchStep has both built in: func: render for Go-templated files, func: prompt for input that gracefully degrades to a default when nobody's at the keyboard.
The scaffold task
prompt asks for the name interactively; in CI it silently uses the default (or a --var). render turns a template plus that name into file contents:
name: scaffold
defaults:
service: "widget"
tasks:
# `orchstep run new` (prompts for the name)
# `orchstep run new --var service_name=billing` (no prompt)
new:
desc: "Scaffold a new service from templates"
steps:
- name: service_name
func: prompt
args:
message: "Service name"
type: text
default: "{{ vars.service }}"
outputs:
name: "{{ result.value }}"
- name: main_go
func: render
args:
template: |
package main
// {{ steps.service_name.name }} service
func main() {
println("{{ steps.service_name.name }} listening on :8080")
}
outputs:
code: "{{ result.output }}"
- name: dockerfile
func: render
do: "FROM golang:1.22 AS build # {{ steps.service_name.name }}"
outputs:
content: "{{ result.output }}"
- name: summary
func: shell
do: echo "scaffolded {{ steps.service_name.name }}/main.go and Dockerfile"The demo keeps each rendered file in an outputs: value so it's safe to run anywhere. To actually write the files, add output_file: to a render step:
- name: main_go
func: render
args:
template_file: "templates/main.go.tmpl"
output_file: "services/{{ steps.service_name.name }}/main.go"Note the path itself is templated — the service name decides where the file lands. That's the find-and-replace step you used to do by hand, done by the engine.
One workflow, human and robot
The prompt function is what makes this both an interactive tool and a CI primitive. At your terminal you get a real prompt with a default. In a pipeline — where stdin isn't a terminal — it skips the prompt and uses the default, or whatever you pass:
# Interactive: asks "Service name", offers the default
orchstep run new
# Scripted: no prompt, name supplied
orchstep run new --var service_name=billingSame YAML, no if CI branch. The blog demo for testing made the case for "one command everywhere"; this is the same idea applied to a generator — the thing you run by hand on Friday is the thing your platform automation calls on Monday.
What you actually gained
| Concern | Copy + find-replace | OrchStep new task |
|---|---|---|
| Naming the service | manual search-replace | prompt → steps.service_name.name |
| Where files land | move them by hand | templated output_file: path |
| Stale leftover metadata | easy to miss | rendered fresh from templates |
| Interactive vs scripted | two procedures | one task, prompt adapts |
| Lives in | tribal knowledge | the repo |
Where OrchStep is not the answer
If you're scaffolding whole framework apps with deep conventions, a dedicated generator (Yeoman, cookiecutter, your framework's own CLI) will out-feature a handful of render steps. This pattern is the sweet spot for your house-style service — the five or ten files that are 90% boilerplate and 10% the name — without taking on a generator framework to produce them.
Where to go next
- render — templating files, with
template_fileandoutput_file - prompt — interactive input that degrades to defaults in CI
- Variables & Outputs — wiring
promptoutput into later steps - Quick Start — your first workflow in two minutes
Still cloning last quarter's service and find-replacing the name? Turn it into one orchstep run new. Install the binary and start with the demo above.
curl -fsSL https://orchstep.dev/install.sh | sh