TechWhale

Back

The smallest possible internal developer platform — a golden-path CLI scaffolding a service, shown in a terminalThe smallest possible internal developer platform — a golden-path CLI scaffolding a service, shown in a terminal

The first run worked perfectly. node bin/create-service.mjs --name payments-api --owner checkout-team, and thirty seconds later I had a full service directory.

A Dockerfile. A GitHub Actions CI workflow. Kubernetes Deployment and Service manifests. A working health-check endpoint. A new line in a catalog file saying this thing existed.

No ticket, no platform team, no meeting. I built the whole tool in an afternoon, and it felt, briefly, like magic.

Then I ran it a second time with a typo — Payments_API instead of payments-api — and got a wall of raw Node stack trace instead of a helpful error message. throw new Error(...), no try/catch anywhere near it, straight into the terminal.

That’s the moment a piece of “self-service platform” tooling shows you what it actually is underneath the pitch deck: somebody’s shortcut, hardened exactly as much as its first real user forced it to be, and not one bit more.

I want to be upfront about why I built this instead of writing another “here’s how platform engineering works” roundup. I don’t run a platform team. I haven’t spent three years fighting golden-path adoption across forty squads.

What I have is the same thing every DevOps trend piece in 2026 is telling me to care about — “platform engineering,” “internal developer platforms,” “golden paths” — without a single one explaining what those words point to mechanically, underneath the org chart and the vendor deck.

So I built the dumbest possible thing that could honestly be called a golden path, to find out, before deciding whether Backstage or Port or Humanitec were worth recommending to anyone.

Thirty seconds to fix, and nobody fixes it until someone hits it#

The tool itself is unglamorous on purpose: a Node script, no dependencies, about ninety lines. It takes --name and --owner, copies a template directory, replaces a handful of placeholders, writes the result to generated/<name>, and appends a line to a catalog.yaml. That’s the entire platform.

Here’s the part that broke:

function main() {
  const args = parseArgs(process.argv.slice(2));
  validateName(args.name);
  // ...
}

main(); // <- nothing catches the throw
js

validateName does the right thing — it rejects Payments_API with a real, specific reason. But nothing between main() and the terminal catches the error.

So the “helpful” rejection message arrives wrapped in a five-line stack trace, pointing at line numbers in a file the caller has never opened. The fix took thirty seconds:

try {
  main();
} catch (err) {
  console.error(`✖ ${err.message}`);
  process.exit(1);
}
js

I’m not telling you this because it’s a hard bug — it’s the easiest bug in the file. I’m telling you because of when it got fixed: after I hit it, not before.

Every “self-service” tool ships with a version of this gap, because the person building it only ever runs the happy path. They typed the command correctly, because they’re the one who wrote the validation regex.

The gap between “works when I demo it” and “survives contact with someone who isn’t me” is most of what a platform team’s actual job is. It’s invisible on every roadmap slide about golden paths, because roadmap slides don’t include stack traces.

What a golden path actually is, mechanically#

Once the tool ran cleanly, I could see the shape of it clearly enough to say what a golden path actually is, stripped of the platform-team language. It’s exactly three things.

  1. A template. A directory of files with placeholders in them — a Dockerfile, a CI workflow, some Kubernetes manifests, a README. Nothing an engineer couldn’t hand-write in a day.
  2. A mechanical copy-and-fill step. Read the template, substitute the placeholders, write the result somewhere. My version does this with String.split().join() in a loop. Not sophisticated. Doesn’t need to be.
  3. A place the result gets registered, so it’s discoverable by something other than “ask in Slack who owns this.” My version is a YAML file with one entry per service. Backstage calls this the software catalog — mechanically, a list with an owner column.

That’s the whole trick. Everything Backstage, Port, and Humanitec sell beyond this — a web UI, auth and RBAC, a plugin ecosystem, TechDocs, scorecards that shame teams into fixing their READMEs — is real, and some of it is genuinely hard to build yourself.

But it’s value added on top of this three-step core, not a categorically different thing.

Once you’ve seen the core, you can price the extra: is the plugin ecosystem and the UI worth the operational cost of running Backstage for your team’s size — or are you buying a product to solve a problem three engineers and an afternoon already solved for you?

What the toy doesWhat Backstage/Port add
catalog.yaml, one entry per serviceSearchable UI, ownership graphs, dependency mapping
String-replace on a template dirTemplating engine, multi-step scaffolder actions, form UI
Nothing — errors are your problemAuth, RBAC, audit log of who scaffolded what
One language, one templatePlugin ecosystem, TechDocs, a marketplace of golden paths

Where the toy stops working#

Two limitations showed up almost immediately, and neither is fixable by writing more code in the same style. They’re the actual reason platform engineering became its own discipline instead of staying “a script someone wrote.”

The first: my tool only has one template, a Node service. The instant a second team asks for a Python golden path, or a Go one, or “we’re actually a Java shop,” the string-replace approach starts multiplying.

Not one template to maintain, but N templates, each drifting from the others in ways nobody notices until an audit. Real platform teams solve this with a proper scaffolding engine — Backstage’s scaffolder actions, Cookiecutter, Copier — precisely because “just add another folder” stops scaling around the third language.

The second is worse, and Backstage doesn’t fully solve it either: template drift. If I improve the Dockerfile tomorrow — pin a base image digest, add a non-root user I forgot — payments-api never finds out.

It was stamped out of the template at a moment in time and stays there, permanently, unless someone manually re-applies the improvement. Teams running Backstage at scale handle this with scheduled bots that open bulk PRs across every service generated from a given template.

That’s not a platform feature you get for free. It’s an entire extra piece of automation that most golden-path writeups skip past in one sentence.

The question that actually matters#

I’m not going to tell you whether to adopt platform engineering. That depends on your team size, your ticket queue, and how many “who owns this service” Slack threads you’re currently tolerating — none of which I know about your org.

What I’d push back on is the question most 2026 trend pieces put in front of you: “should you adopt platform engineering.” That’s the wrong question, because it’s really a purchasing decision disguised as a strategy one.

The right question is the one this afternoon answered for me: do you understand the three-step core well enough to know exactly what you’d be paying Backstage, Port, or Humanitec for?

The UI. The auth model. The plugin ecosystem. The maintenance of drift-detection bots. Versus what a sharp engineer could give your team in a week with a template folder and a YAML file.

Sometimes the honest answer is “yes, buy the product, we’re past the point where duct tape scales.” Sometimes it’s “we have four services, write the ninety lines.” Both are legitimate. Only one of them is currently getting a conference talk.

I Built the Smallest Possible Internal Developer Platform, Just to See What One Actually Is
https://techwhale.in/the-smallest-possible-internal-developer-platform/
Author Mayur Chavhan
Published at July 26, 2026

Related posts