Introduction
As organizations scale, the number of services, libraries, pipelines, and infrastructure components grows exponentially. Without a centralized way to discover and understand software, teams face “software chaos” โ orphaned services, duplicated efforts, and knowledge silos. Backstage, originally created at Spotify and now a CNCF incubating project, provides an open platform for building developer portals that solve these challenges.
This article explores Backstage architecture, software catalog design, implementation patterns, and how to build a developer portal that accelerates engineering productivity.
What is a Developer Portal?
A developer portal is a centralized platform that provides:
- Software inventory: All services, libraries, and components in one place
- Ownership information: Clear ownership and responsibility
- Documentation: Centralized docs, READMEs, and API specs
- Self-service actions: Provisioning, deployments, and tooling
- Discovery: Find what exists and who owns it
The Problem Backstage Solves
Organizations face several challenges:
- Discovery: Engineers can’t find existing services or libraries
- Ownership: Unclear who owns which components
- Onboarding: New engineers spend weeks finding relevant systems
- Tool sprawl: Multiple tools with inconsistent interfaces
- Documentation rot: Docs scattered and often outdated
Backstage Architecture
Core Components
Backend
- Entity storage (PostgreSQL)
- Ingestion pipeline for catalog data
- Authentication and authorization
- Plugin backend services
Frontend
- React-based SPA
- Configurable plugins
- Extensible UI components
Plugin System
- Frontend plugins for UI
- Backend plugins for APIs
- Shared libraries for common functionality
Architecture Diagram
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Backstage Frontend โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Catalog โ โ TechDocs โ โ Plugins โ โ Search โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโดโโโโโโโโโโ
โ Backstage API โ
โโโโโโโโโโโฌโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Plugins โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Catalog โ โ GitHub โ โ Jenkins โ โ Kubernetesโ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ External Systems โ
โ GitHub โ Jira โ Jenkins โ PagerDuty โ Datadog โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Software Catalog Design
Entity Model
Backstage uses a declarative entity model. Each software component is defined as a YAML file:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-service
description: Payment processing microservice
tags:
- java
- spring-boot
- payments
links:
- url: https://github.com/company/payment-service
title: Repository
- url: https://payment-service.dev.company.com
title: Dashboard
spec:
type: service
lifecycle: production
owner: payments-team
system: checkout
Entity Types
| Type | Description |
|---|---|
| Component | Services, websites, libraries |
| API | API definitions (OpenAPI, GraphQL) |
| Resource | Databases, storage, queues |
| System | Collection of components |
| User | Developer accounts |
| Group | Teams and organizations |
Relationships
Entities can reference each other:
spec:
system: checkout
providesApis:
- payment-api
dependsOn:
- resource:stripe-api
- component:customer-service
Implementation Patterns
Pattern 1: Git-Based Catalog
Store entity definitions alongside code:
# Repository structure
services/
โโโ payment-service/
โ โโโ catalog-info.yaml
โ โโโ src/
โ โโโ README.md
โโโ customer-service/
โ โโโ catalog-info.yaml
โ โโโ src/
catalog-info.yaml:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-service
spec:
owner: payments-team
type: service
Pattern 2: Dynamic Discovery
Ingest entities from external systems:
// Custom catalog processor
import { CatalogClient } from '@backstage/catalog-client';
class CustomProcessor implements CatalogProcessor {
async readLocation(
location: LocationSpec,
_optional: boolean,
emit: CatalogProcessorEmit
): Promise<boolean> {
const entities = await fetchFromExternalSystem();
for (const entity of entities) {
emit({
entity: normalizeEntity(entity),
location,
});
}
return true;
}
}
Pattern 3: Template-Based Scaffolding
Create new services from templates:
apiVersion: backstage.io/v1alpha1
kind: Template
metadata:
name: microservice-template
title: Microservice Template
spec:
steps:
- id: fetch-base
name: Fetch Base
action: fetch:template
input:
url: ./template
values:
componentId: ${{ parameters.componentId }}
- id: publish
name: Publish
action: publish:github
input:
repoUrl: ${{ parameters.repoUrl }}
description: ${{ parameters.description }}
- id: register
name: Register
action: catalog:register
input:
catalogInfoUrl: ${{ steps.publish.output.catalogInfoUrl }}
Building Custom Plugins
Frontend Plugin
// plugins/metrics/src/plugin.ts
import { createPlugin } from '@backstage/core-plugin';
import { metricsRouteRef } from './routes';
export const metricsPlugin = createPlugin({
id: 'metrics',
routes: {
metrics: metricsRouteRef,
},
});
export const MetricsPage = metricsPlugin.provide(
createRouteRef({
path: '/metrics',
title: 'Service Metrics',
}),
// Component implementation
);
Backend Plugin
// plugins/metrics-backend/src/service/router.ts
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export async function createRouter(
env: PluginEnvironment,
): Promise<Router> {
const router = Router();
router.get('/metrics/:componentId', async (req, res) => {
const { componentId } = req.params;
const metrics = await fetchMetricsFromPrometheus(componentId);
res.json(metrics);
});
return router;
}
Integration Examples
GitHub Integration
Automatically register repositories:
// app-config.yaml
catalog:
providers:
github:
- organization: company
filters:
repositories:
- payment-.*
schedule:
frequency: { minutes: 30 }
timeout: { minutes: 3 }
Kubernetes Integration
Discover running services:
import { KubernetesClient } from '@backstage/plugin-kubernetes-backend';
const client = new KubernetesClient();
const services = await client.getServices('payment-namespace');
// Transform to Backstage entities
CI/CD Integration
Link pipelines and deployments:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
annotations:
backstage.io/ci: github-actions
backstage.io/deployment: production
Platform Engineering Integration
Backstage as IDP Core
Backstage integrates with platform engineering:
- Self-service provisioning: Create resources via templates
- Golden paths: Pre-configured architectures
- Infrastructure as Code: Terraform modules in catalog
- Policy enforcement: Automated compliance checks
Actions Registry
Backstage Actions enable automation:
// Custom action for provisioning
import { createCustomAction } from '@backstage/plugin-scaffolder-backend';
export const provisionDatabase = createCustomAction({
id: 'database:provision',
description: 'Provisions a new database',
schema: {
input: z.object({
name: z.string(),
tier: z.enum(['dev', 'staging', 'prod']),
}),
},
handler: async (ctx) => {
const db = await terraform.provision({
name: ctx.input.name,
tier: ctx.input.tier,
});
ctx.output('databaseUrl', db.url);
},
});
Best Practices
Entity Design
- Consistent naming: Use kebab-case for names
- Rich metadata: Include tags, links, descriptions
- Clear ownership: Always specify owner
- Lifecycle tracking: Mark deprecated components
Governance
- Catalog policies: Enforce required annotations
- Ownership mandate: Require owner field
- Review process: Approve new components
- Stale detection: Flag inactive components
Operations
- Incremental adoption: Start with key services
- Automated ingestion: Reduce manual entry
- Search optimization: Configure elasticsearch
- Monitoring: Track catalog health
Tools and Extensions
Popular Plugins
| Plugin | Purpose |
|---|---|
| catalog-backend-module-github | GitHub discovery |
| kubernetes | K8s integration |
| pagerduty | On-call and incidents |
| opsgenie | Alert management |
| sonarqube | Code quality |
| tech-radar | Technology standards |
| api-docs | API documentation |
Alternative Platforms
- Port: Commercial Backstage fork
- Backstage.io: CNCF open source version
- DevHub: Atlassian’s offering
Resources
- Backstage Official Documentation
- CNCF Backstage Project
- Spotify Engineering Blog
- Backstage Marketplace
Conclusion
Backstage provides a powerful foundation for building developer portals that solve software discovery and management challenges. By creating a centralized software catalog, organizations enable engineers to find existing components, understand ownership, and self-service common tasks. The plugin architecture allows customization for organization-specific needs while the open-source foundation ensures long-term viability.
Comments