0x55aa
Back to Blog

🎭 Backstage in Real Life: The Developer Portal That Almost Worked

|
5 min read

Every platform team eventually hits the same slide in the same internal deck: "We need a single pane of glass." Someone's seen a conference talk, someone's read the Spotify engineering blog, and within a sprint you've got a Backstage instance running in a namespace called platform with a very shiny homepage and exactly four services registered in the catalog — three of which are stale.

I've stood up Backstage twice now, at two different companies, and both times the pattern was identical: incredible week-one demo, tumbleweed by week six. Not because Backstage is bad software — it's genuinely well built — but because a developer portal is a social problem wearing a technical costume. You can npx @backstage/create-app your way to a UI in twenty minutes. Getting engineers to treat it as the source of truth takes months.

The Catalog Is Only as Good as Its Laziest Contributor

Backstage's entire value proposition rests on catalog-info.yaml files existing, being accurate, and staying accurate. That third part is where it dies.

# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payments-api
  description: Handles payment intents and webhook reconciliation
  annotations:
    github.com/project-slug: cubet/payments-api
    pagerduty.com/service-id: PXXXXXX
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: checkout

This looks trivial to write once. The problem is nobody remembers to write it again when the service gets a new owner, gets deprecated, or gets split into two services during a Q3 refactor. Six months in, the catalog is a museum of org-chart decisions nobody's told the portal about. Engineers open it, see a team listed as the owner of a service that team hasn't touched in a year, lose trust, and go back to asking in Slack. Once trust goes, it doesn't come back with a design refresh.

The fix that actually worked for us wasn't more documentation nagging people to update YAML — it was making the catalog entry a required, CI-enforced part of the repo, generated from things that already had to be true:

# .github/workflows/catalog-check.yml
- name: Validate catalog-info.yaml exists and owner is a real team
  run: |
    test -f catalog-info.yaml || (echo "::error::missing catalog-info.yaml" && exit 1)
    OWNER=$(yq '.spec.owner' catalog-info.yaml)
    curl -sf "https://api.github.com/orgs/cubet/teams/$OWNER" > /dev/null \
      || (echo "::error::owner '$OWNER' is not a real GitHub team" && exit 1)

Failing the PR if the owner field points at a team that doesn't exist sounds harsh, but it converts "the catalog might be stale" into "the catalog is provably not lying to you about ownership," which is a much smaller promise and much easier to keep.

Software Templates Are the Actual Killer Feature, Not the Catalog

Here's the thing nobody tells you in the getting-started docs: the catalog is the least interesting part of Backstage. The part that actually earns its keep is Software Templates — the scaffolder that spits out a fully wired-up repo (CI, lint config, Dockerfile, catalog entry, PagerDuty hookup, the works) from a form.

# templates/node-service/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: node-microservice
  title: New Node.js microservice
spec:
  parameters:
    - properties:
        name:
          type: string
        owner:
          type: string
          ui:field: OwnerPicker
  steps:
    - id: fetch
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
    - id: publish
      action: publish:github
      input:
        repoUrl: github.com?owner=cubet&repo=${{ parameters.name }}
    - id: register
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

This is the moment the portal stops being a passive directory and becomes something engineers reach for because it's genuinely the fastest path to a new service — faster than copy-pasting a sibling repo and manually stripping out the old service name from nine files (we've all done it, we've all missed one). And crucially: every service created this way is born with a correct catalog entry, so you're not fighting the staleness problem after the fact, you're preventing it at creation time.

At Cubet, once we moved the "spin up a new service" instructions from a wiki page into a scaffolder template, adoption of the catalog itself went up as a side effect — people weren't registering services out of civic duty, they were registering services because that's just what clicking the button did.

TechDocs: Docs-as-Code, But Only If the Portal Actually Renders Them Well

The other underrated win is TechDocs — MkDocs-based docs that live in the repo next to the code and render inside Backstage. The pitch is "docs that can't drift because they're versioned with the code that they describe," which is true, but only if you resist the urge to also keep a separate Confluence space "just in case." Pick one. If engineers aren't sure where the docs live, they'll default to whichever is easier, and Confluence is always easier to half-write and abandon.

What Actually Matters

If you're evaluating Backstage, or already regretting installing it: the catalog needs to be provably true or it's worse than useless, the scaffolder is what actually changes behavior, and the whole thing lives or dies on whether it's the path of least resistance, not just the officially sanctioned option. Nobody adopts a developer portal because leadership mandated it. They adopt it because it's genuinely less annoying than the alternative.

If your org has a Backstage instance gathering dust, don't reach for more onboarding docs first — go find the one workflow engineers do most often (usually "spin up a new service" or "find who owns this thing that's paging me") and make the portal the fastest way to do exactly that one thing. Everything else follows from there.

Share:LinkedInXHacker News

Thanks for reading!

Back to all posts