Steven's Knowledge

Getting Started

Stand up Backstage locally, register a service in the catalog, create a scaffolder template, deploy a generated service

Getting Started

This page stands up Backstage, the most popular OSS developer portal, and walks through the core loops: catalog, scaffolding, and TechDocs.

Prerequisites

  • Node 18+, Yarn 4+
  • Docker for the backend database
  • Optional: GitHub PAT for catalog discovery and scaffolder integration

Create a Backstage App

npx @backstage/create-app@latest
# Follow prompts: app name 'my-idp'
cd my-idp

yarn install --immutable
yarn dev

Backstage starts at http://localhost:3000. You see the catalog (empty), TechDocs, and an API browser.

Register Your First Service

Each service has a catalog-info.yaml at its repo root:

# in github.com/my-org/checkout-service/catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: checkout-service
  description: Handles the checkout flow
  tags: [golang, payments]
  annotations:
    github.com/project-slug: my-org/checkout-service
    backstage.io/techdocs-ref: dir:.
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: payments

In Backstage UI: Create → Register existing component → paste URL of the catalog-info.yaml. Backstage fetches it and adds the service.

Define the team in another entity:

# Catalog can hold groups + users too
apiVersion: backstage.io/v1alpha1
kind: Group
metadata: { name: team-payments }
spec:
  type: team
  profile:
    displayName: Payments Team
    email: payments@my-org.com
  children: []

Now the catalog entry links to a real team. Click "team-payments" and you see all services they own.

Discovery: Auto-Ingest Everything

Manually registering 200 services doesn't scale. Configure GitHub discovery:

# app-config.yaml
integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}

catalog:
  providers:
    github:
      myCompany:
        organization: 'my-org'
        catalogPath: '/catalog-info.yaml'
        schedule:
          frequency: { minutes: 30 }
          timeout: { minutes: 3 }

Backstage scans your GitHub org every 30 minutes for catalog-info.yaml files. Self-registering catalog.

Build a Scaffolder Template

A template generates a fully-wired new service. Create a template repo:

templates/golang-service/
├── template.yaml        # the template definition
└── skeleton/            # files that get rendered
    ├── catalog-info.yaml
    ├── README.md
    ├── Dockerfile
    ├── .github/workflows/ci.yaml
    └── cmd/server/main.go

template.yaml:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: golang-service-template
  title: Go HTTP Service
  description: Bootstrap a new Go HTTP service with CI, k8s, observability
spec:
  owner: team-platform
  type: service

  parameters:
    - title: Basics
      required: [name, owner]
      properties:
        name:
          type: string
          title: Service name
          pattern: '^[a-z][a-z0-9-]+$'
        owner:
          type: string
          title: Owning team
          ui:field: OwnerPicker
          ui:options:
            allowedKinds: [Group]
        description:
          type: string
          title: Description

  steps:
    - id: fetch
      name: Fetch and render
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
          description: ${{ parameters.description }}

    - id: publish
      name: Publish to GitHub
      action: publish:github
      input:
        repoUrl: github.com?repo=${{ parameters.name }}&owner=my-org
        defaultBranch: main

    - id: register
      name: Register in catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: '/catalog-info.yaml'

  output:
    links:
      - title: Repo
        url: ${{ steps.publish.output.remoteUrl }}
      - title: Open in catalog
        icon: catalog
        entityRef: ${{ steps.register.output.entityRef }}

skeleton/catalog-info.yaml:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: ${{ values.name }}
  description: ${{ values.description }}
  annotations:
    github.com/project-slug: my-org/${{ values.name }}
spec:
  type: service
  lifecycle: experimental
  owner: ${{ values.owner }}

Register the template:

# app-config.yaml
catalog:
  locations:
    - type: url
      target: https://github.com/my-org/templates/blob/main/golang-service/template.yaml
      rules: [{ allow: [Template] }]

Now in Backstage: Create Component → Go HTTP Service → fill form → service repo + catalog entry. The form gives an engineer a complete, conventional starting point in minutes.

TechDocs: Docs Next to Code

Add mkdocs.yml and a docs/ directory to your service repo:

checkout-service/
├── catalog-info.yaml
├── mkdocs.yml
├── docs/
│   ├── index.md
│   ├── runbook.md
│   └── architecture.md
└── ...

mkdocs.yml:

site_name: 'Checkout Service'
nav:
  - Overview: index.md
  - Runbook: runbook.md
  - Architecture: architecture.md
plugins: [techdocs-core]

Backstage's TechDocs reads markdown directly from the repo and renders it on the service page. No separate doc site to maintain.

Add Plugins

Backstage's value is plugins. Common ones:

  • GitHub Actions — see workflow runs per service
  • Kubernetes — pods/deployments per service
  • Datadog / Prometheus / Grafana — embed dashboards
  • PagerDuty — show on-call and incidents
  • TechRadar — visualize tech adoption
  • Sentry / Rollbar — error tracking
  • Cost Insights — show service cost (FinOps integration)
  • Scorecards — service maturity tracking (3rd party: Roadie, etc.)

Each plugin needs config and (usually) a token. Document carefully which plugins you've enabled.

Production Deployment

For real use:

  • Database: PostgreSQL, not the dev SQLite.
  • Auth: OIDC against your IdP (Okta, Google, Auth0, Keycloak).
  • Container image: build with the Backstage Docker workflow.
  • Persistence: TechDocs cache, scaffolder workspace.
  • Backstage runs in your own cluster (typical Helm chart deploy).
yarn build-image
docker build -t my-backstage .
# Push to registry, deploy to cluster

A full production setup is a 1-2 sprint platform engineering project — that's what makes Port and Cortex (SaaS) attractive alternatives.

What's Next

  • Patterns — golden paths, scorecards, Crossplane integration, secrets
  • Best Practices — platform-as-product, adoption, scaling team, pitfalls

On this page