Backstage Developer Portal Installation
Backstage is an open-source internal developer portal created by Spotify that centralizes service catalogs, documentation, and scaffolding templates for platform engineering teams. This guide covers installing Backstage on Linux, configuring the software catalog, setting up TechDocs, and integrating plugins for Kubernetes and CI/CD visibility.
Prerequisites
- Ubuntu 20.04/22.04 or similar Linux distribution
- Node.js 18.x or 20.x
- Yarn 1.22+
- Git
- PostgreSQL 13+ (for production; SQLite works for development)
- 2 GB RAM minimum (4 GB recommended)
Install Node.js and Yarn
# Install Node.js 20.x via NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verify installation
node --version # Should output v20.x.x
npm --version
# Install Yarn globally
npm install -g yarn
# Install build tools (needed for native modules)
sudo apt install -y build-essential python3
Create a Backstage App
# Create a new Backstage application
npx @backstage/create-app@latest
# When prompted:
# - Enter app name: my-developer-portal
# - Choose database: PostgreSQL (for production)
cd my-developer-portal
# Install dependencies
yarn install
# Start in development mode (both frontend and backend)
yarn dev
Backstage starts on http://localhost:3000 (frontend) and http://localhost:7007 (backend).
Configure the Software Catalog
The software catalog is Backstage's core feature. Services register themselves via catalog-info.yaml files:
# catalog-info.yaml (place in each service repository)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-service
description: Handles payment processing
annotations:
github.com/project-slug: my-org/payment-service
backstage.io/techdocs-ref: dir:.
tags:
- java
- payments
- critical
spec:
type: service
lifecycle: production
owner: payments-team
system: ecommerce
dependsOn:
- component:default/database-service
Configure catalog locations in app-config.yaml:
# app-config.yaml
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location, Template]
locations:
# Static catalog entries
- type: file
target: ../../catalog/all-components.yaml
# GitHub discovery - auto-registers all repos with catalog-info.yaml
- type: github-discovery
target: https://github.com/my-org
# Set up GitHub integration in app-config.yaml
# Add your GitHub token for catalog discovery
cat >> app-config.yaml <<'EOF'
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
EOF
Set Up TechDocs
TechDocs renders Markdown documentation from your repositories directly in Backstage:
# Install the TechDocs CLI
npm install -g @techdocs/cli
# Install MkDocs and required plugins
pip3 install mkdocs mkdocs-techdocs-core
Add a mkdocs.yml to each service repository:
# mkdocs.yml
site_name: Payment Service
site_description: Documentation for the payment processing service
docs_dir: docs
plugins:
- techdocs-core
nav:
- Home: index.md
- Architecture: architecture.md
- API Reference: api.md
- Runbook: runbook.md
Configure TechDocs in app-config.yaml:
techdocs:
builder: 'local' # Use 'external' for production (S3, GCS)
generator:
runIn: 'local'
publisher:
type: 'local'
local:
publishDirectory: '/tmp/techdocs'
For production, use an external storage backend:
techdocs:
builder: 'external'
publisher:
type: 'awsS3'
awsS3:
bucketName: my-techdocs-bucket
region: us-east-1
credentials:
accessKeyId: ${AWS_ACCESS_KEY_ID}
secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
Kubernetes Plugin Integration
The Kubernetes plugin shows pod status and deployment health directly on a component's page:
# Add the Kubernetes plugin
yarn --cwd packages/app add @backstage/plugin-kubernetes
yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend
Configure Kubernetes clusters in app-config.yaml:
kubernetes:
serviceLocatorMethod:
type: 'multiTenant'
clusterLocatorMethods:
- type: 'config'
clusters:
- url: https://k8s.example.com
name: production
authProvider: 'serviceAccount'
skipTLSVerify: false
serviceAccountToken: ${K8S_SA_TOKEN}
caData: ${K8S_CA_DATA}
- url: https://staging.k8s.example.com
name: staging
authProvider: 'serviceAccount'
serviceAccountToken: ${K8S_STAGING_SA_TOKEN}
Annotate components to link them to Kubernetes resources:
# In catalog-info.yaml
metadata:
annotations:
backstage.io/kubernetes-id: payment-service
backstage.io/kubernetes-namespace: production
Running Backstage in Production
# Build the production bundle
yarn build
# Configure PostgreSQL connection in app-config.production.yaml
cat > app-config.production.yaml <<'EOF'
backend:
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
database: backstage
baseUrl: https://backstage.example.com
cors:
origin: https://backstage.example.com
app:
baseUrl: https://backstage.example.com
EOF
# Start the production server
NODE_ENV=production node packages/backend/dist/index.js \
--config app-config.yaml \
--config app-config.production.yaml
Create a systemd service for production:
sudo tee /etc/systemd/system/backstage.service > /dev/null <<'EOF'
[Unit]
Description=Backstage Developer Portal
After=network.target postgresql.service
[Service]
Type=simple
User=backstage
WorkingDirectory=/opt/backstage
EnvironmentFile=/opt/backstage/.env
ExecStart=/usr/bin/node packages/backend/dist/index.js \
--config app-config.yaml \
--config app-config.production.yaml
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable backstage
sudo systemctl start backstage
Plugin Development Basics
# Scaffold a new plugin
yarn new --select plugin
# Plugin structure
packages/
└── plugins/
└── my-plugin/
├── src/
│ ├── index.ts # Plugin entry point
│ ├── plugin.ts # Plugin definition
│ └── components/ # React components
└── package.json
// src/plugin.ts
import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api';
export const myPlugin = createPlugin({
id: 'my-plugin',
routes: {
root: rootRouteRef,
},
});
export const MyPluginPage = myPlugin.provide(
createRoutableExtension({
name: 'MyPluginPage',
component: () => import('./components/MyPage').then(m => m.MyPage),
mountPoint: rootRouteRef,
}),
);
Troubleshooting
Catalog entities not appearing:
# Check backend logs for discovery errors
yarn workspace backend start 2>&1 | grep -i "error\|warn"
# Force a catalog refresh via the API
curl -X POST http://localhost:7007/api/catalog/refresh \
-H "Content-Type: application/json" \
-d '{"entityRef":"component:default/payment-service"}'
Database connection errors:
# Verify PostgreSQL credentials
psql -h localhost -U backstage -d backstage -c "SELECT 1;"
# Check backend config is loading correctly
NODE_ENV=development node packages/backend/dist/index.js \
--config app-config.yaml -- 2>&1 | head -50
TechDocs not rendering:
# Build docs locally to test
techdocs-cli build --source-dir . --output-dir /tmp/techdocs-test
# Verify MkDocs is in PATH
which mkdocs && mkdocs --version
Conclusion
Backstage provides a unified developer experience by centralizing service ownership, documentation, and infrastructure visibility in a single portal. With the software catalog, TechDocs, and Kubernetes plugin configured, your teams gain self-service access to the context they need to operate services confidently. Expand capabilities further with community plugins from the Backstage plugin marketplace.


