Building an Internal Developer Platform with Backstage
The Backstage developer portal has become the industry standard for internal developer platforms, used by companies like Spotify, Netflix, and American Airlines. It provides a unified interface for service catalogs, documentation, CI/CD pipelines, and infrastructure provisioning. Therefore, developers spend less time searching for information and more time building products. Originally open-sourced by Spotify in 2020 and later donated to the Cloud Native Computing Foundation, it now sits in the CNCF Incubating tier with a large plugin ecosystem behind it.
Platform engineering is about reducing cognitive load on development teams by providing self-service tools and golden paths. Moreover, the plugin architecture lets you integrate every tool in your ecosystem into a single portal. Consequently, new developers can onboard faster, existing teams can discover services easily, and best practices are codified as templates. The framework itself is a React frontend plus a Node.js backend, so customizing it means writing TypeScript rather than wrestling with a closed SaaS configuration screen.
Backstage Developer Portal: Software Catalog
The software catalog is the core feature — a centralized registry of all services, libraries, APIs, and infrastructure in your organization. Each entity is defined by a YAML descriptor file stored alongside the code it describes. Furthermore, the catalog automatically tracks ownership, dependencies, and lifecycle status. Because the descriptor lives in the repository, ownership metadata travels with the code and stays accurate as teams reorganize.
# catalog-info.yaml — lives in each repo
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: order-service
description: Handles order lifecycle from creation to fulfillment
annotations:
github.com/project-slug: myorg/order-service
backstage.io/techdocs-ref: dir:.
pagerduty.com/service-id: P123ABC
grafana/dashboard-selector: "order-service"
tags:
- java
- spring-boot
- grpc
links:
- url: https://grafana.internal/d/orders
title: Grafana Dashboard
- url: https://confluence.internal/display/ORDERS
title: Architecture Docs
spec:
type: service
lifecycle: production
owner: team-commerce
system: e-commerce-platform
dependsOn:
- component:inventory-service
- component:payment-service
- resource:orders-database
providesApis:
- order-api
consumesApis:
- inventory-api
- payment-api
How the Catalog Is Ingested and Modeled
Entities do not appear by magic. The catalog runs a set of processors on a schedule, fetching descriptor files from locations you register — typically through the GitHub discovery provider that scans org repositories for catalog-info.yaml. Each processor validates the entity against its schema, resolves relations, and emits errors you can surface in the UI when a descriptor is malformed.
The data model is deliberately small but expressive. Components represent runnable software, Systems group related components, Domains group systems, and Resources describe infrastructure like databases or queues. APIs are first-class entities, which is what makes the dependency graph meaningful. For instance, a query like “which components consume the payment-api?” becomes answerable across the whole organization, so a breaking API change can be assessed before it ships rather than after an incident.
In practice, teams enforce a baseline of required metadata through a custom processor or a CI lint step. As a result, every service must declare an owner and a lifecycle stage, which prevents the catalog from rotting into a graveyard of orphaned entries. This discipline matters more than any single feature; a catalog that nobody trusts is worse than no catalog at all.
Scaffolder: Golden Path Templates
The scaffolder enables teams to create new services, libraries, and infrastructure through standardized templates. Instead of copying an existing repo and modifying it — which introduces drift — developers fill out a form and get a properly configured project with CI/CD, monitoring, and documentation already wired up. The template itself is just another catalog entity, so it shows up in search and carries its own ownership and tags.
# template.yaml — Spring Boot service template
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: spring-boot-service
title: Spring Boot Microservice
description: Production-ready Spring Boot service with CI/CD and observability
tags:
- java
- spring-boot
- recommended
spec:
owner: platform-team
type: service
parameters:
- title: Service Details
required: [name, owner, description]
properties:
name:
title: Service Name
type: string
pattern: '^[a-z][a-z0-9-]*