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.
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.
Part 2
Your project after this mission · 4 files change
- checkout-api/
- catalog-info.yamlnew
- shoplite-portal/
- catalog/
- org.yamlnew
- systems.yamlnew
- app-config.yamlmodified
Part 3
Build it, step by step
- 1
Create the app
The generator creates a monorepo with
packages/app(frontend) andpackages/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-portalcd shoplite-portal && yarn start── expected output ──Successfully created shoplite-portal...[0] webpack compiled successfully[1] Listening on :7007App available at http://localhost:3000 - 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
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
Discover every repo automatically
Instead of registering files one by one, the GitHub discovery provider scans the organisation for
catalog-info.yamlfiles on a schedule. Install@backstage/plugin-catalog-backend-module-githubin 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.yamlappear 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.
Part 5
Interview questions from this mission
What is Backstage and what are its core features?
Why is a service catalog valuable?