Skip to main content
โšก Calmops

Backstage Developer Portal: Building a Software Catalog for Modern Engineering

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:

  1. Self-service provisioning: Create resources via templates
  2. Golden paths: Pre-configured architectures
  3. Infrastructure as Code: Terraform modules in catalog
  4. 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

  1. Consistent naming: Use kebab-case for names
  2. Rich metadata: Include tags, links, descriptions
  3. Clear ownership: Always specify owner
  4. Lifecycle tracking: Mark deprecated components

Governance

  1. Catalog policies: Enforce required annotations
  2. Ownership mandate: Require owner field
  3. Review process: Approve new components
  4. Stale detection: Flag inactive components

Operations

  1. Incremental adoption: Start with key services
  2. Automated ingestion: Reduce manual entry
  3. Search optimization: Configure elasticsearch
  4. Monitoring: Track catalog health

Tools and Extensions

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

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