- devops
- l2
- topic-pack
- backstage
- platform-engineering --- Portal | Level: L2: Operations | Topics: Backstage & Developer Portals, Platform Engineering | Domain: DevOps & Tooling
Backstage & Developer Portals - Primer¶
Why This Matters¶
Who made it: Backstage was created at Spotify and open-sourced in March 2020. It became a CNCF Incubating project in 2022. Spotify built it internally to manage over 2,000 microservices across hundreds of engineering teams. The name "Backstage" reflects the idea of the infrastructure and tooling that operates behind the scenes of a production.
Backstage is Spotify's open-source platform for building developer portals. It gives engineering organizations a single pane of glass for discovering services, creating new projects, and reading documentation — replacing the scattered wikis, spreadsheets, and tribal knowledge that teams typically rely on. For DevOps, Backstage is the reference implementation of platform engineering: it encodes golden paths (opinionated, well-supported workflows) so teams can ship safely without deep infrastructure expertise. Without it, developers waste hours finding who owns a service, how to create a new one correctly, or where the documentation lives. Backstage centralizes all of this and makes it self-service.
Core Concepts¶
1. Software Catalog¶
The catalog is a centralized registry of every service, library, API, and resource in your organization. Each entity is defined by a catalog-info.yaml file checked into its repo, making ownership and dependency information version-controlled and discoverable.
Minimal catalog-info.yaml:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: order-service
description: Handles order creation, payment, and fulfillment
annotations:
github.com/project-slug: myorg/order-service
backstage.io/techdocs-ref: dir:.
tags:
- python
- fastapi
links:
- url: https://grafana.example.com/d/order-service
title: Grafana Dashboard
icon: dashboard
spec:
type: service
lifecycle: production
owner: group:team-platform
system: ecommerce
providesApis:
- order-api
consumesApis:
- payment-api
- inventory-api
dependsOn:
- resource:order-database
Entity kinds: | Kind | Purpose | |------|---------| | Component | A software component (service, library, website) | | API | An API definition (OpenAPI, gRPC, GraphQL, AsyncAPI) | | Resource | Infrastructure (database, S3 bucket, queue) | | System | A collection of related components | | Domain | A business domain grouping systems | | Group | A team or organizational unit | | User | An individual person | | Template | A software template for the scaffolder | | Location | A pointer to other catalog files to ingest |
Register entities in Backstage:
# catalog-locations.yaml (registered in app-config.yaml)
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: all-services
spec:
type: url
targets:
- https://github.com/myorg/order-service/blob/main/catalog-info.yaml
- https://github.com/myorg/payment-service/blob/main/catalog-info.yaml
Auto-discovery (scan entire GitHub org):
# app-config.yaml
catalog:
providers:
github:
myOrg:
organization: 'myorg'
catalogPath: '/catalog-info.yaml'
schedule:
frequency: { minutes: 30 }
timeout: { minutes: 3 }
2. Software Templates (Scaffolder)¶
Templates let developers create new services with CI/CD, monitoring, and catalog registration baked in from day one.
# templates/python-service/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: python-fastapi-service
title: Python FastAPI Service
description: Creates a new FastAPI service with CI/CD, Docker, and monitoring
tags:
- python
- fastapi
- recommended
spec:
owner: group:team-platform
type: service
parameters:
- title: Service Details
required:
- name
- owner
properties:
name:
title: Service Name
type: string
pattern: '^[a-z0-9-]+$'
description: Lowercase with hyphens only
owner:
title: Owner Team
type: string
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
description:
title: Description
type: string
- title: Infrastructure
properties:
database:
title: Database
type: string
enum: [none, postgresql, redis]
default: none
port:
title: Port
type: number
default: 8000
steps:
- id: fetch
name: Fetch Template
action: fetch:template
input:
url: ./skeleton
values:
name: ${{ parameters.name }}
owner: ${{ parameters.owner }}
description: ${{ parameters.description }}
database: ${{ parameters.database }}
port: ${{ parameters.port }}
- id: publish
name: Create Repository
action: publish:github
input:
repoUrl: github.com?owner=myorg&repo=${{ parameters.name }}
repoVisibility: internal
defaultBranch: main
- id: register
name: Register in Catalog
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'
output:
links:
- title: Repository
url: ${{ steps.publish.output.remoteUrl }}
- title: Open in Catalog
icon: catalog
entityRef: ${{ steps.register.output.entityRef }}
Analogy: Think of the Software Catalog as the "CMDB for the microservices era." Traditional CMDBs (Configuration Management Databases) tried to centrally track infrastructure but went stale because humans had to update them. Backstage catalog-info.yaml files live in the repo, so they are updated by the same developer workflow that changes the code.
3. TechDocs (Docs-as-Code)¶
TechDocs renders Markdown documentation (via MkDocs) directly inside the portal, co-located with the service it describes.
Setup in a service repo:
# mkdocs.yml (in service repo root)
site_name: Order Service
plugins:
- techdocs-core
nav:
- Home: index.md
- Architecture: architecture.md
- Runbooks: runbooks.md
- API Reference: api.md
# catalog-info.yaml annotation
metadata:
annotations:
backstage.io/techdocs-ref: dir:.
# Tells Backstage to look for mkdocs.yml in the repo root
# docs/index.md (written by the service team)
# Order Service
## Overview
The order service handles order creation, payment processing, and fulfillment tracking.
## Architecture
...
## Runbooks
- [High latency](runbooks.md#high-latency)
- [Database connection errors](runbooks.md#db-errors)
TechDocs builder configuration (app-config.yaml):
techdocs:
builder: 'local' # or 'external' for CI-built docs
generator:
runIn: 'local' # or 'docker'
publisher:
type: 'local' # or 'awsS3', 'googleGcs', 'azureBlobStorage'
# For production:
# type: 'awsS3'
# awsS3:
# bucketName: 'my-techdocs-bucket'
4. Plugins¶
Backstage is extensible through plugins. The plugin ecosystem connects external tools into one developer experience.
Common plugins: | Plugin | Purpose | |--------|---------| | @backstage/plugin-kubernetes | View pod status, logs, events per service | | @backstage/plugin-github-actions | View CI/CD workflow status | | @pagerduty/backstage-plugin | On-call status, incidents per service | | @backstage/plugin-cost-insights | Cloud cost per service/team | | @backstage/plugin-scaffolder | Software templates (built-in) | | @backstage/plugin-techdocs | Documentation (built-in) | | @backstage/plugin-search | Search across catalog, docs, code |
Installing a plugin:
# From the Backstage app directory
cd packages/app
yarn add @backstage/plugin-kubernetes
# Add to app routing (packages/app/src/App.tsx)
# import { KubernetesPage } from '@backstage/plugin-kubernetes';
# <Route path="/kubernetes" element={<KubernetesPage />} />
5. Backstage Setup and Configuration¶
# Create a new Backstage app
npx @backstage/create-app@latest
# Follow prompts, creates a full Backstage project
# Project structure
my-backstage/
app-config.yaml # main configuration
app-config.local.yaml # local overrides (gitignored)
packages/
app/ # frontend (React)
backend/ # backend (Node.js)
plugins/ # custom plugins
# Run in development mode
cd my-backstage
yarn dev # starts frontend + backend
# Build for production
yarn build
yarn build:backend
# Docker
yarn build-image
# Creates a Docker image for the backend
Core app-config.yaml:
app:
title: My Company Developer Portal
baseUrl: http://localhost:3000
backend:
baseUrl: http://localhost:7007
listen:
port: 7007
database:
client: pg
connection:
host: localhost
port: 5432
user: backstage
password: ${POSTGRES_PASSWORD}
auth:
providers:
github:
development:
clientId: ${GITHUB_CLIENT_ID}
clientSecret: ${GITHUB_CLIENT_SECRET}
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
catalog:
rules:
- allow: [Component, System, API, Resource, Location, Template, Group, User, Domain]
locations:
- type: file
target: ../../catalog-info.yaml
- type: url
target: https://github.com/myorg/service-catalog/blob/main/all.yaml
6. Operational Patterns¶
Catalog ownership enforcement:
Every service must have an owner in catalog-info.yaml. This enables on-call routing, cost attribution, and security responsibility.
Gotcha: Backstage's default SQLite database is fine for local development but does not scale. In production, switch to PostgreSQL early. Migrating data from SQLite to PostgreSQL after you have hundreds of catalog entities registered is painful and underdocumented.
Interview tip: If asked about "platform engineering" in an interview, Backstage is the reference implementation to cite. The key insight: a developer portal reduces cognitive load by providing golden paths -- opinionated, pre-configured templates -- rather than forcing every team to figure out CI, monitoring, and deployment from scratch.
Golden paths via templates: Templates encode organizational standards. Instead of a wiki page saying "when creating a new service, remember to set up CI, monitoring, and logging," the template does it automatically.
Service maturity scoring: Use the Backstage Scorecards plugin or custom annotations to track service maturity:
metadata:
annotations:
myorg/maturity-level: "3" # 1-5 scale
myorg/has-runbooks: "true"
myorg/has-slo: "true"
myorg/has-oncall: "true"
Search integration:
# app-config.yaml
search:
collators:
techdocs:
schedule:
frequency: { minutes: 10 }
catalog:
schedule:
frequency: { minutes: 10 }
Quick Reference¶
# Create new Backstage app
npx @backstage/create-app@latest
# Development
yarn dev # start dev server (frontend + backend)
yarn start # frontend only
yarn start-backend # backend only
# Build
yarn build && yarn build:backend # production build
yarn build-image # Docker image
# Key files
# app-config.yaml — main configuration
# catalog-info.yaml — entity definition (per repo)
# mkdocs.yml — TechDocs configuration (per repo)
# template.yaml — scaffolder template definition
# Key URLs (default)
# http://localhost:3000 — frontend
# http://localhost:7007 — backend API
# http://localhost:7007/api/catalog/entities — catalog API
Wiki Navigation¶
Related Content¶
- Platform Engineering Flashcards (CLI) (flashcard_deck, L1) — Platform Engineering
- Platform Engineering Patterns (Topic Pack, L2) — Platform Engineering