Skip to content
Hogin Hogin
Go back

Backstage in a weekend: an IDP and golden paths for a small team

8 мин чтения

Platform engineering gets sold as a story about companies with hundreds of teams: their own catalog, their own golden paths, a dedicated platform engineer on staff. A team of three to five people has the natural reaction — “that’s not for us, we all already know how things work.” The problem is that “we all know” holds up right up until the first vacation or the first person who quits, and then the knowledge of how to stand up a new service lives in a Slack thread and one person’s head. A minimal Backstage isn’t about scale — it’s about not losing that knowledge.

Table of contents

Open Table of contents

An IDP is not a platform — it’s three concrete things

Internal Developer Platform sounds heavyweight, but in practice it’s three components, each plugging its own hole.

Each of these can be roughly faked on its own: a catalog as a Notion table, a golden path as a wiki article, a scaffolder as a cookiecutter template nobody updates. It works for a while. It breaks exactly when the team grows a bit or the lineup changes — the Notion table goes stale, nobody reads the article to the end, and nobody maintains the template forked from someone’s old repo.

Without a catalog, knowledge lives in heads; with one, it lives in one place

Why now, and not two years ago

Not long ago, standing up your own Backstage meant maintaining a separate Node.js monorepo, learning a plugin API that changed between minor versions, and spending weeks on it, not a weekend. What changed:

The upshot: something that used to be its own project with its own roadmap now fits into a weekend’s worth of config.

A minimal Backstage in docker-compose

This isn’t a production config with full auth and HA — it’s a working minimum to see a catalog and your first Software Template live in one evening.

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: backstage
      POSTGRES_PASSWORD: backstage
      POSTGRES_DB: backstage
    volumes:
      - backstage-db:/var/lib/postgresql/data

  backstage:
    image: ghcr.io/backstage/backstage:1.32.0
    depends_on:
      - postgres
    ports:
      - "7007:7007"
    environment:
      POSTGRES_HOST: postgres
      POSTGRES_PORT: 5432
      POSTGRES_USER: backstage
      POSTGRES_PASSWORD: backstage
      GITHUB_TOKEN: ${GITHUB_TOKEN}
    volumes:
      - ./app-config.production.yaml:/app/app-config.production.yaml

volumes:
  backstage-db:

The key idea in this file is that all customization lives in the mounted app-config.production.yaml, not in a rebuilt image. That file describes the integration with your GitHub/GitLab (where the catalog reads catalog-info.yaml from repos), TechDocs (markdown docs right in the UI), and the list of Software Templates:

catalog:
  locations:
    - type: url
      target: https://github.com/acme/catalog-registry/blob/main/catalog-info.yaml
  providers:
    github:
      acmeOrg:
        organization: "acme"
        catalogPath: "/catalog-info.yaml"
        schedule:
          frequency: { minutes: 30 }

The github provider walks the organization every 30 minutes and pulls catalog-info.yaml from every repo that has one — that’s automatic catalog population without adding services by hand one at a time.

Software Template: a new service in one click

A catalog without a scaffolder is just a list you have to maintain by hand. The real payoff starts with a Software Template — a YAML description of steps that Backstage runs when a developer clicks “Create” in the UI.

A minimal template for a new Go service:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: golden-path-go-service
  title: "Go service (golden path)"
spec:
  parameters:
    - properties:
        name:
          type: string
          title: "Service name"
  steps:
    - id: fetch
      name: "Generate from skeleton"
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}

    - id: publish
      name: "Create git repository"
      action: publish:github
      input:
        repoUrl: "github.com?owner=acme&repo=${{ parameters.name }}"

    - id: register
      name: "Register in the catalog"
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: "/catalog-info.yaml"

The ./skeleton folder is just a regular set of template files with ${{ values.name }} placeholders: a Dockerfile, a Helm chart with readiness/liveness probes already wired up, a .github/workflows/ci.yaml with linting, tests, and image build, and catalog-info.yaml itself, so the service shows up in the catalog the moment the repo is created.

The result for a developer: three clicks in the UI — service name, “Create,” confirm — and out comes a git repo with working CI that’s already green on the first commit, because the skeleton already went through this pipeline on other services.

From clicking in the UI to green CI with no manual steps

Comparison: without a catalog vs. minimal Backstage

Without an IDPMinimal Backstage
Where to find the service listSlack, memory, Notion if you’re luckyCatalog, updates automatically
New serviceCopy-paste from a “similar” repo + manual editsSoftware Template, 3 clicks
CI for a new serviceSet up from scratch or copied with bugsAlready in the skeleton, proven on prior services
Onboarding a new engineerVerbal explanations + guessworkGolden path is already the documented norm
Service owner”I think Pete built this”Explicitly listed in catalog-info.yaml

Traps that even small teams fall into

How to verify it works

Three quick checks after the first run. First, the catalog is alive: curl -s localhost:7007/api/catalog/entities | jq length should return a number greater than zero once the GitHub provider has run at least one sync cycle. Second, the actual scenario: run a Software Template from parameters to completion and confirm a new repo really shows up on GitHub with a running workflow. Third, the whole point of the exercise: open Actions in the freshly created repo and see CI pass green with zero manual fixes after generation.

Bottom line

An IDP isn’t a corporate ritual for companies with a dedicated platform department — it’s a way to codify, once, how your team correctly builds services, and stop explaining it verbally to every new hire. The starting cost is one evening on docker-compose and one Software Template for the most common service type in your stack. It pays for itself by the second or third new service, which now comes up in minutes instead of copy-paste riddled with someone else’s bugs.


Share this post:

Next Post
AI SRE agents in 2026: what they actually do and where they break