Backstage Developer Portal Setup Guide
Backstage developer portal has become the de facto standard for internal developer platforms. Originally created at Spotify, this open-source platform now powers developer experience at thousands of organizations. It provides a unified service catalog, documentation hub, and extensible plugin ecosystem that eliminates the fragmentation of internal tooling.
This guide walks you through building a production-ready Backstage instance with a populated service catalog, custom plugins, and software templates. Whether you are starting a platform engineering initiative or consolidating existing tools, this guide covers the practical steps to deliver value quickly.
Architecture Overview
Backstage consists of three core components: the frontend app (React), the backend (Node.js), and a PostgreSQL database for persistent storage. The plugin architecture allows teams to extend functionality without modifying the core platform.
# Create a new Backstage app
npx @backstage/create-app@latest my-portal
cd my-portal
# Project structure
# ├── app-config.yaml # Main configuration
# ├── app-config.production.yaml
# ├── packages/
# │ ├── app/ # Frontend (React)
# │ └── backend/ # Backend (Node.js)
# └── plugins/ # Custom pluginsConfiguring the Service Catalog
The service catalog is the heart of any Backstage developer portal. It provides a centralized registry of all software components, APIs, resources, and their ownership. Moreover, it automatically discovers and ingests catalog entities from your Git repositories.
# app-config.yaml
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location, Group, User]
locations:
# GitHub org discovery
- type: github-discovery
target: https://github.com/my-org/*/blob/main/catalog-info.yaml
# Static locations
- type: url
target: https://github.com/my-org/backstage-catalog/blob/main/all-systems.yaml
processors:
githubOrg:
providers:
- target: https://github.com
apiBaseUrl: https://api.github.com
orgs: ['my-org']Each service registers itself with a catalog-info.yaml file in its repository root:
# catalog-info.yaml in each service repo
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-service
description: Handles payment processing and billing
annotations:
github.com/project-slug: my-org/payment-service
backstage.io/techdocs-ref: dir:.
pagerduty.com/service-id: PABCDEF
grafana/dashboard-selector: "payment-*"
tags:
- java
- spring-boot
- payments
links:
- url: https://grafana.internal/d/payment
title: Grafana Dashboard
icon: dashboard
spec:
type: service
lifecycle: production
owner: team-payments
system: billing-platform
providesApis:
- payment-api
consumesApis:
- user-api
- notification-api
dependsOn:
- resource:default/payments-db
- resource:default/payments-redisBuilding Custom Plugins
Backstage plugins extend the portal with new features. Furthermore, the plugin SDK provides a structured approach to building both frontend and backend extensions:
# Generate a new plugin
cd my-portal
npx @backstage/cli new --select plugin
# Enter plugin ID: cost-insights// plugins/cost-insights/src/components/CostDashboard.tsx
import React from 'react';
import { useApi, configApiRef } from '@backstage/core-plugin-api';
import { Table, TableColumn } from '@backstage/core-components';
interface ServiceCost {
name: string;
monthlyCost: number;
trend: number;
owner: string;
}
export const CostDashboard = () => {
const [costs, setCosts] = React.useState<ServiceCost[]>([]);
const config = useApi(configApiRef);
React.useEffect(() => {
fetch('/api/cost-insights/services')
.then(res => res.json())
.then(data => setCosts(data));
}, []);
const columns: TableColumn<ServiceCost>[] = [
{ title: 'Service', field: 'name' },
{ title: 'Monthly Cost', field: 'monthlyCost',
render: row => `$${row.monthlyCost.toFixed(2)}` },
{ title: 'Trend', field: 'trend',
render: row => row.trend > 0
? `+${row.trend}%`
: `${row.trend}%` },
{ title: 'Owner', field: 'owner' },
];
return (
<Table
title="Service Cost Overview"
columns={columns}
data={costs}
options={{ pageSize: 20, search: true }}
/>
);
};Software Templates for Golden Paths
Software templates codify best practices into self-service project scaffolding. As a result, developers can create new services, libraries, or infrastructure components that automatically follow organizational standards:
# templates/spring-service/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: spring-boot-service
title: Spring Boot Microservice
description: Create a production-ready Spring Boot microservice
spec:
owner: team-platform
type: service
parameters:
- title: Service Details
required: [name, owner, description]
properties:
name:
title: Service Name
type: string
pattern: '^[a-z][a-z0-9-]*$'
owner:
title: Owner Team
type: string
ui:field: OwnerPicker
description:
title: Description
type: string
javaVersion:
title: Java Version
type: string
enum: ['17', '21']
default: '21'
- title: Infrastructure
properties:
database:
title: Database
type: string
enum: ['none', 'postgresql', 'mysql']
default: 'postgresql'
messaging:
title: Messaging
type: string
enum: ['none', 'kafka', 'rabbitmq']
steps:
- id: fetch
name: Fetch Template
action: fetch:template
input:
url: ./skeleton
values:
name: '{{ parameters.name }}'
owner: '{{ parameters.owner }}'
java_version: '{{ parameters.javaVersion }}'
- id: publish
name: Create Repository
action: publish:github
input:
repoUrl: 'github.com?repo={{ parameters.name }}&owner=my-org'
description: '{{ parameters.description }}'
- id: register
name: Register in Catalog
action: catalog:register
input:
repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}'
catalogInfoPath: /catalog-info.yamlTechDocs Integration
TechDocs transforms Markdown documentation into a searchable knowledge base. Consequently, every service has documentation accessible from the same portal:
# app-config.yaml
techdocs:
builder: 'external'
generator:
runIn: 'local'
publisher:
type: 'awsS3'
awsS3:
bucketName: 'my-techdocs-bucket'
region: 'us-east-1'
credentials:
roleArn: 'arn:aws:iam::123456789:role/techdocs-publisher'When NOT to Use Backstage
Backstage requires significant investment in setup and maintenance. Small teams with fewer than 20 developers may find the overhead unjustified when simpler wiki-based solutions suffice. Additionally, if your organization lacks dedicated platform engineering resources, the portal may become stale and unused. Backstage is also not a monitoring solution — it aggregates links to monitoring tools but does not replace Grafana or Datadog.
Key Takeaways
- A Backstage developer portal centralizes service discovery, documentation, and tooling in a single platform
- The service catalog with automatic GitHub discovery eliminates manual registry maintenance
- Custom plugins extend the portal with team-specific features like cost insights and deployment tracking
- Software templates enforce organizational standards while enabling developer self-service
- Plan for dedicated platform engineering resources — Backstage is not a set-and-forget solution
Related Reading
- Golden Paths in Platform Engineering
- Kubernetes Network Policies and Zero Trust
- Terraform Stacks Multi-Environment Infrastructure