Command Palette

Search for a command to run...

Mission 1.1 · Stage 1 — The Developer Portal: Backstage and Golden Paths

Backstage and the Service Catalog

Goal: A running Backstage app whose catalog lists every ShopLite service, API, team, and resource, with owners, discovered automatically from Git.

60 min Free locally 4 steps 1 break-it drills

By the end of this mission

  • Create and run a Backstage app
  • Model services with the catalog's entity kinds (Component, API, Resource, System, Group)
  • Register entities with catalog-info.yaml and discover them automatically from GitHub
  • Explain why ownership metadata is the foundation of everything else

Part 1

Understand it first

What Backstage is

Backstage is a FRAMEWORK for building a developer portal: a React frontend and Node.js backend you create from a template, configure, extend with plugins, and deploy yourself. Its three core features are the SOFTWARE CATALOG (what exists and who owns it), SOFTWARE TEMPLATES (create new things the golden-path way, Mission 1.2), and TECHDOCS (docs-as-code, Mission 1.3). Everything else (Kubernetes, Argo CD, CI, cost, security) comes from plugins. Hosted alternatives exist (Roadie, Spotify Portal, Port, Cortex) if you'd rather not run it.

The catalog model

Entities are YAML with the same shape as Kubernetes objects. COMPONENT: a piece of software (a service, website, library). API: an interface a component provides or consumes (OpenAPI, AsyncAPI for Kafka topics, gRPC). RESOURCE: infrastructure a component depends on (a PostgreSQL database, an S3 bucket, a Kafka topic). SYSTEM: a group of components and resources that deliver one capability ('checkout'). DOMAIN: a group of systems. GROUP and USER: teams and people, usually synced from your identity provider. RELATIONS (ownedBy, dependsOn, providesApi, consumesApi, partOf) connect them into a graph.

OWNERSHIP is the most valuable field: every incident, security finding, and cost report needs 'which team owns this?'. A catalog where every entity has a real owner is worth more than any plugin.

The checkout system in the catalogdiagram
Rendering diagram…

Part 2

Your project after this mission · 4 files change

shoplite-platform/
  • checkout-api/
    • catalog-info.yamlnew
  • shoplite-portal/
    • catalog/
      • org.yamlnew
      • systems.yamlnew
    • app-config.yamlmodified

Part 3

Build it, step by step

  1. 1

    Create the app

    The generator creates a monorepo with packages/app (frontend) and packages/backend. It runs with SQLite in memory locally; production uses PostgreSQL (and runs as a container in your cluster, deployed through GitOps like any other service).

    terminal
    $ npx @backstage/create-app@latest --path shoplite-portal
    cd shoplite-portal && yarn start
    ── expected output ──
    Successfully created shoplite-portal
    ...
    [0] webpack compiled successfully
    [1] Listening on :7007
    App available at http://localhost:3000
  2. 2

    Describe the organisation

    Teams and people. In production these are synced from GitHub teams, Okta, or Entra ID with an org provider; a static file is fine to start.

    shoplite-portal/catalog/org.yamlwhole fileyaml
    apiVersion: backstage.io/v1alpha1
    kind: Group
    metadata: { name: team-checkout, title: Checkout Team }
    spec:
      type: team
      profile: { email: checkout@shoplite.dev }
      children: []
      members: [asha, ravi]
    ---
    apiVersion: backstage.io/v1alpha1
    kind: Group
    metadata: { name: team-platform, title: Platform Team }
    spec: { type: team, children: [], members: [mei] }
    ---
    apiVersion: backstage.io/v1alpha1
    kind: User
    metadata: { name: asha }
    spec: { profile: { displayName: Asha Rao, email: asha@shoplite.dev }, memberOf: [team-checkout] }
  3. 3

    Describe a service where its code lives

    Each repo carries its own catalog-info.yaml, so the team that owns the code maintains its metadata in the same PR as code changes. Annotations connect plugins: which GitHub repo, which Kubernetes label selects its pods, which Argo CD app deploys it, and where its docs live.

    checkout-api/catalog-info.yamlwhole fileyaml
    apiVersion: backstage.io/v1alpha1
    kind: Component
    metadata:
      name: checkout-api
      description: Takes carts to paid orders.
      tags: [java, spring-boot]
      links:
        - { url: https://grafana.shoplite.dev/d/checkout, title: Dashboard, icon: dashboard }
        - { url: https://runbooks.shoplite.dev/checkout, title: Runbook }
      annotations:
        github.com/project-slug: shoplite/checkout-api
        backstage.io/kubernetes-label-selector: app=checkout
        argocd/app-selector: service=checkout
        backstage.io/techdocs-ref: dir:.
    spec:
      type: service
      lifecycle: production
      owner: group:team-checkout
      system: checkout
      providesApis: [checkout-v1, order-events]
      consumesApis: [catalog-v2]
      dependsOn: [resource:checkout-db]
    ---
    apiVersion: backstage.io/v1alpha1
    kind: API
    metadata: { name: checkout-v1 }
    spec:
      type: openapi
      lifecycle: production
      owner: group:team-checkout
      system: checkout
      definition:
        $text: ./openapi.yaml
    ---
    apiVersion: backstage.io/v1alpha1
    kind: Resource
    metadata: { name: checkout-db, description: Checkout PostgreSQL (RDS) }
    spec: { type: database, owner: group:team-checkout, system: checkout }
  4. 4

    Discover every repo automatically

    Instead of registering files one by one, the GitHub discovery provider scans the organisation for catalog-info.yaml files on a schedule. Install @backstage/plugin-catalog-backend-module-github in the backend (backend.add(import('@backstage/plugin-catalog-backend-module-github'))) and configure it. A GitHub App (not a personal token) gives it read access.

    shoplite-portal/app-config.yamladd to fileyaml
    integrations:
      github:
        - host: github.com
          apps:
            - $include: github-app-credentials.yaml   # from a Kubernetes Secret in production
    
    catalog:
      rules:
        - allow: [Component, System, API, Resource, Location, Group, User, Domain, Template]
      locations:
        - { type: file, target: ../../catalog/org.yaml }
        - { type: file, target: ../../catalog/systems.yaml }
      providers:
        github:
          shoplite:
            organization: shoplite
            catalogPath: /catalog-info.yaml
            filters: { branch: main }
            schedule: { frequency: { minutes: 30 }, timeout: { minutes: 3 } }
    terminal
    $ yarn start
    # then open http://localhost:3000/catalog?filters[kind]=component
    ── expected output ──
    [1] catalog info Read 23 GitHub repositories (14 matching the pattern) type=plugin target=github-provider:shoplite
    [1] catalog info Committed 41 entities for github-provider:shoplite

Checkpoint — you should now have

  • ✓Backstage runs locally and the catalog shows ShopLite components, APIs, resources, and teams.
  • ✓checkout-api's page shows its owner, system, API docs, and dependency graph.
  • ✓New repos with a catalog-info.yaml appear automatically within 30 minutes.

Part 4

Break it on purpose

Make each change, run the command, and read the error before revealing the diagnosis. Recognising these messages on sight is what makes you fast on a real team. Undo the change afterwards.

Break #1

Entity with a dangling owner

A component declares owner: group:checkout-team, but the group is named team-checkout.

terminal
$ curl -s localhost:7007/api/catalog/entities/by-name/component/default/checkout-api | jq '.relations | map(select(.type=="ownedBy"))'
── what you'll see ──
[ { "type": "ownedBy", "targetRef": "group:default/checkout-team" } ]
# UI: "Owner: checkout-team" with a broken link, and the entity is missing from the team's page

Part 5

Interview questions from this mission

01

What is Backstage and what are its core features?

02

Why is a service catalog valuable?

0/3 · 0%