The shape
What every service gets
Runs safely
Two replicas, readiness-gated rollouts that never drop below capacity, startup, readiness and liveness probes, CPU and memory requests and limits, and a PodDisruptionBudget.
Observable from the first deploy
Prometheus metrics (runtime, HTTP request count and latency by route, database pool) and a ServiceMonitor that adds the platform's identity labels. JSON logs with one access-log line per request. See Observability.
Reachable
A public HTTPS endpoint at <service>.<env>.gerodimos.dev with a landing page, while /metrics is answered with a 404 at the load balancer, so only in-cluster Prometheus sees it.
Ready for a database
The chart ships the database binding switched off. A Release switches it on per environment, without touching the service's code. See Service bindings. A migrations/ directory and a schema workflow that checks and packages it come with it. See Database schemas.
Shippable
CI runs Go tests, helm lint and chart unit tests, then pushes an image tagged with the commit, which is what a Release deploys. When migrations/ changes, a second workflow checks them and publishes a schema package the same way.
Discoverable
A catalog-info.yaml with the owning team, so the service appears in Backstage without anyone registering it.
How it works
-
Pick a template and a version
Backstage's Onboard Service form lists the template's release tags and defaults to the newest. The choice is recorded on the
Component.spec: scaffold: {template: golang-service, version: "0.13.0"} -
Render once, as one commit
component-operator creates the repository and a
ScaffoldRequest; scaffold-operator renders the tagged template into it as a single commit and records exactly which template commit it used. It never touches the repository again. -
Deliberately simple templating
Only files ending in
.tplare rendered, and only exact{{ parameterName }}placeholders are replaced. Helm templates and GitHub Actions expressions pass through untouched, because they belong to engines that run later. -
Versioned like a product
Each template has its own SemVer tags, and a release check fails unless the template's declared version matches the tag. A change can be tried on a real generated repository (tests, chart lint, image build) before it's tagged.
Design choices
Conventions in the template, not the API
Probes, replicas, metrics and endpoints are template defaults, not Component fields. The API stays a small identity record, and a team can still change any default in its own repository.
Render once, then hand over
The team owns its repository from the first commit. Nothing reaches back in and overwrites its changes.
Trade-off: existing services don't get template improvements automatically; moving to a newer version is a pull request in each service's repository.
The platform contract is built in
The chart puts the platform's component and environment labels on every workload. Everything that finds services by those labels (dashboards, the portal, Argo CD lookups) works for a new service with no setup.
Dumb templating on purpose
No logic in the template engine means no surprises in generated files, and no collision with Helm's or GitHub Actions' own {{ }} syntax.
Not built yet
- A second template.
golang-serviceis the only one; adding another is adding a directory. - Assisted upgrades. Nothing yet opens the upgrade pull request for services on an older template version.