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.