---
title: "Setting Up and Configuring Backstage"
description: "Step-by-step operational guide for installing Backstage, configuring the software catalog, setting up TechDocs, creating scaffolder templates, and integrating with GitHub."
url: https://agent-zone.ai/knowledge/platform-engineering/backstage-setup/
section: knowledge
date: 2026-02-22
categories: ["platform-engineering"]
tags: ["backstage","software-catalog","techdocs","scaffolder","plugins","spotify","developer-portal"]
skills: ["backstage-administration","catalog-configuration","template-authoring","plugin-development"]
tools: ["backstage","nodejs","yarn","docker","github","postgresql","mkdocs"]
levels: ["intermediate"]
word_count: 1281
formats:
  json: https://agent-zone.ai/knowledge/platform-engineering/backstage-setup/index.json
  html: https://agent-zone.ai/knowledge/platform-engineering/backstage-setup/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Setting+Up+and+Configuring+Backstage
---


## What Backstage Provides

Backstage is an open-source developer portal originally built by Spotify, now a CNCF Incubating project. It serves as the single UI layer for an internal developer platform, unifying the service catalog, documentation, scaffolding templates, and plugin-based integrations behind one interface. It does not replace your tools — it provides a consistent frontend for discovering and interacting with them.

The core components:

- **Software Catalog**: A registry of all services, libraries, APIs, and infrastructure components, populated from YAML descriptor files in your repositories.
- **TechDocs**: Documentation-as-code powered by MkDocs, rendered directly in the Backstage UI alongside the service it describes.
- **Scaffolder**: A template engine that creates new projects from predefined templates — repositories, CI pipelines, Kubernetes manifests, and all.
- **Plugins**: Backstage's extension mechanism. The community provides plugins for Kubernetes, ArgoCD, PagerDuty, GitHub Actions, Terraform, and hundreds of other tools.

## Installation

Backstage requires Node.js 18+ and Yarn. Create a new Backstage app:

```bash
npx @backstage/create-app@latest
```

This scaffolds a full application with a frontend (`packages/app`), backend (`packages/backend`), and configuration (`app-config.yaml`). The process takes a few minutes as it installs dependencies.

Start the development server:

```bash
cd my-backstage-app
yarn dev
```

This runs the frontend on port 3000 and the backend on port 7007. The development mode uses an in-memory SQLite database. For production, switch to PostgreSQL.

## PostgreSQL Configuration

Edit `app-config.yaml` (or create `app-config.production.yaml` for production overrides):

```yaml
backend:
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
```

Backstage manages its own schema migrations. On first startup with the PostgreSQL backend, it creates the required tables automatically.

## Configuring the Software Catalog

The catalog is the heart of Backstage. Every entity — service, API, library, team, system — is defined in a `catalog-info.yaml` file at the root of its repository.

A minimal service definition:

```yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-service
  description: "Handles payment processing and billing"
  annotations:
    github.com/project-slug: "myorg/payment-service"
    backstage.io/techdocs-ref: dir:.
  tags:
    - python
    - payments
  links:
    - url: https://grafana.internal/d/payment-service
      title: Dashboard
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: billing
  providesApis:
    - payment-api
  dependsOn:
    - component:user-service
    - resource:payments-db
```

Key fields: `spec.owner` maps to a Group or User entity (critical for ownership tracking), `spec.system` groups related components, and `metadata.annotations` configure plugin behavior.

Register catalog sources in `app-config.yaml`:

```yaml
catalog:
  locations:
    # Scan all repos in the org for catalog-info.yaml
    - type: github-discovery
      target: https://github.com/myorg/*/blob/main/catalog-info.yaml
    # Or register individual locations
    - type: url
      target: https://github.com/myorg/payment-service/blob/main/catalog-info.yaml
  rules:
    - allow: [Component, API, System, Domain, Resource, Group, User, Location]
```

The `github-discovery` provider scans all repositories in an organization and registers any that contain a `catalog-info.yaml`. This is the preferred approach — adding new services to the catalog requires only committing the YAML file.

## Defining Systems, APIs, and Teams

Beyond components, define the organizational structures:

```yaml
# team.yaml
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: team-payments
  description: "Payment and billing team"
spec:
  type: team
  children: []
  members:
    - user:jane.doe
    - user:john.smith
---
# system.yaml
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  name: billing
  description: "End-to-end billing and payment processing"
spec:
  owner: team-payments
  domain: commerce
---
# api.yaml
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: payment-api
  description: "Payment processing REST API"
spec:
  type: openapi
  lifecycle: production
  owner: team-payments
  system: billing
  definition:
    $text: ./openapi.yaml
```

Store these in a central repository (e.g., `backstage-catalog`) or co-locate them with the services they describe. The catalog resolves references across files — `dependsOn: component:user-service` links to whatever entity has `metadata.name: user-service`.

## TechDocs Setup

TechDocs renders MkDocs-based documentation inside Backstage. Each service includes a `mkdocs.yml` and a `docs/` directory.

At the service root:

```yaml
# mkdocs.yml
site_name: Payment Service
nav:
  - Home: index.md
  - Architecture: architecture.md
  - API Reference: api.md
  - Runbook: runbook.md
plugins:
  - techdocs-core
```

With a `docs/` directory containing the Markdown files referenced in `nav`.

Configure TechDocs in `app-config.yaml`:

```yaml
techdocs:
  builder: local        # 'local' for dev, 'external' for production
  generator:
    runIn: docker        # Uses the techdocs-container Docker image
  publisher:
    type: awsS3          # Or googleGcs, azureBlobStorage
    awsS3:
      bucketName: myorg-techdocs
      region: us-east-1
```

For production, use the `external` builder — a CI pipeline generates the static docs site on each merge to main and uploads to object storage. Backstage serves the pre-built docs from the bucket. This is faster and more reliable than building docs on demand.

The CI step to generate and publish docs:

```bash
npx @techdocs/cli generate --source-dir . --output-dir ./site
npx @techdocs/cli publish --publisher-type awsS3 \
  --storage-name myorg-techdocs \
  --entity default/component/payment-service \
  --directory ./site
```

## Scaffolder Templates

The scaffolder creates new projects from templates. A template defines the UI form, input parameters, and a sequence of actions (create repo, write files, open PR, register in catalog).

```yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: new-python-service
  title: Python Microservice
  description: "Create a new Python microservice with FastAPI, Docker, and CI/CD"
  tags:
    - python
    - fastapi
    - recommended
spec:
  owner: team-platform
  type: service
  parameters:
    - title: Service Details
      required:
        - name
        - owner
        - system
      properties:
        name:
          title: Service Name
          type: string
          pattern: "^[a-z][a-z0-9-]*$"
        description:
          title: Description
          type: string
        owner:
          title: Owner
          type: string
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group
        system:
          title: System
          type: string
          ui:field: EntityPicker
          ui:options:
            catalogFilter:
              kind: System
    - title: Infrastructure
      properties:
        database:
          title: Database
          type: string
          enum: ["none", "postgresql", "mysql"]
          default: "none"
        cacheLayer:
          title: Cache
          type: string
          enum: ["none", "redis"]
          default: "none"
  steps:
    - id: fetch
      name: Fetch Skeleton
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          description: ${{ parameters.description }}
          owner: ${{ parameters.owner }}
          system: ${{ parameters.system }}
          database: ${{ parameters.database }}
          cacheLayer: ${{ parameters.cacheLayer }}
    - id: publish
      name: Publish to GitHub
      action: publish:github
      input:
        repoUrl: github.com?owner=myorg&repo=${{ parameters.name }}
        description: ${{ parameters.description }}
        defaultBranch: main
        protectDefaultBranch: true
    - id: register
      name: Register in Catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml
  output:
    links:
      - title: Repository
        url: ${{ steps.publish.output.remoteUrl }}
      - title: Open in Catalog
        icon: catalog
        entityRef: ${{ steps.register.output.entityRef }}
```

The `./skeleton` directory contains the template files with Nunjucks-style placeholders (`${{ values.name }}`). When a developer fills in the form and submits, Backstage creates the repository, populates it with the skeleton files, and registers the new service in the catalog — all in one action.

Store templates in a dedicated repository and register them in the catalog:

```yaml
catalog:
  locations:
    - type: url
      target: https://github.com/myorg/backstage-templates/blob/main/templates/**/template.yaml
```

## GitHub Integration

Configure GitHub authentication and API access in `app-config.yaml`:

```yaml
integrations:
  github:
    - host: github.com
      apps:
        - appId: ${GITHUB_APP_ID}
          privateKey: ${GITHUB_APP_PRIVATE_KEY}
          clientId: ${GITHUB_APP_CLIENT_ID}
          clientSecret: ${GITHUB_APP_CLIENT_SECRET}
          webhookSecret: ${GITHUB_WEBHOOK_SECRET}

auth:
  providers:
    github:
      development:
        clientId: ${AUTH_GITHUB_CLIENT_ID}
        clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
```

Using a GitHub App (rather than a personal access token) is strongly recommended. It provides higher rate limits, granular repository permissions, and organizational visibility. Create the app in your GitHub organization settings with these permissions: Repository contents (read), Pull requests (read/write), Metadata (read), and Members (read).

## Adding Plugins

Plugins extend Backstage with integrations. Install a plugin package and wire it into the frontend or backend.

Example — adding the Kubernetes plugin to see pod status in the service view:

```bash
yarn --cwd packages/app add @backstage/plugin-kubernetes
yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend
```

Configure the cluster connection in `app-config.yaml`:

```yaml
kubernetes:
  serviceLocatorMethod:
    type: multiTenant
  clusterLocatorMethods:
    - type: config
      clusters:
        - url: https://k8s-api.internal:6443
          name: production
          authProvider: serviceAccount
          serviceAccountToken: ${K8S_SA_TOKEN}
          skipTLSVerify: false
```

Then add the Kubernetes tab to the entity page in `packages/app/src/components/catalog/EntityPage.tsx`. The exact wiring depends on the plugin, but it follows a consistent pattern: install the package, add configuration, and mount the component on the entity page.

## Production Deployment

For production, build the Docker image:

```bash
yarn build:backend
docker build -t backstage -f packages/backend/Dockerfile .
```

Deploy to Kubernetes with a Deployment, Service, and Ingress. Key considerations:

- Run at least two replicas behind a load balancer.
- Use the PostgreSQL backend, not SQLite.
- Externalize all secrets (GitHub tokens, database passwords) into Kubernetes Secrets or Vault.
- Set `app.baseUrl` and `backend.baseUrl` to your actual domain.
- Configure the catalog to refresh on a schedule (`catalog.providers.github.schedule` in newer versions) rather than relying solely on webhook-triggered refreshes.

A Helm chart for Backstage is available in the community charts repository, which handles most of the Kubernetes resource configuration.

