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
External Resources
In conclusion, Backstage Developer Portal Plugins is an essential topic for modern software development. By applying the patterns and practices covered in this guide, you can build more robust, scalable, and maintainable systems. Start with the fundamentals, iterate on your implementation, and continuously measure results to ensure you are getting the most value from these approaches.