All Articles

How to Set Up Backstage Internal Developer Portal from Scratch in 2026

Backstage is the open-source Internal Developer Portal (IDP) from Spotify, now used by Netflix, LinkedIn, and thousands of engineering teams. This step-by-step guide shows you how to deploy it, add your services, and integrate it with GitHub and Kubernetes.

DevOpsBoysMar 10, 20269 min read
Share:Tweet

If you have worked at a company with more than 30 engineers, you have felt the pain: nobody knows which services exist, where they are deployed, who owns them, or what they depend on. Documentation lives in 15 different places. New engineers spend their first two weeks just trying to understand what runs in production.

Backstage was built to solve this. Originally created at Spotify to manage thousands of microservices, it was open-sourced in 2020 and is now one of the fastest-growing infrastructure tools in the industry. Netflix, LinkedIn, American Airlines, and thousands of other companies use it as the single interface where engineers can find anything related to their services.

This guide walks you through setting up a production-ready Backstage instance from scratch — creating the app, connecting it to GitHub, registering services in the catalog, and deploying it on Kubernetes.


What Backstage Actually Does

Before setting it up, it is worth understanding what Backstage is — because the name "internal developer portal" does not fully capture it.

Backstage has three core features:

Software Catalog: A centralized registry of all your services, libraries, websites, APIs, and infrastructure. Each item in the catalog is described by a YAML file (called a catalog-info.yaml) that lives in the service's repository. The catalog shows owners, dependencies, documentation links, CI/CD status, and any other metadata you want to attach.

Software Templates (Scaffolder): A system for creating new services from templates. Instead of engineers copying a starter repository and making 20 manual changes, they fill out a form in Backstage and a new repository is created, configured, and registered in the catalog automatically. This is the feature that saves the most time at scale.

TechDocs: Documentation that lives alongside code (as Markdown files in the same repository) and is rendered inside Backstage. The idea is to kill the documentation wiki by making docs a natural part of the service.

Beyond these three, Backstage has a plugin system with hundreds of community plugins: Kubernetes cluster status, GitHub Actions pipelines, PagerDuty incidents, Datadog dashboards, cost data from cloud providers. Almost anything you want to see about a service can be surfaced inside Backstage.


Prerequisites

Before starting, make sure you have:

  • Node.js 20.x or later (node --version)
  • Yarn (npm install -g yarn)
  • Docker (for local testing)
  • A Kubernetes cluster (for deployment — DigitalOcean Kubernetes is straightforward to set up)
  • A GitHub account and personal access token (or GitHub App)
  • kubectl configured to connect to your cluster
  • helm installed

Step 1: Create a New Backstage Application

Backstage provides a CLI that scaffolds a new application with all the defaults. This creates a fully working Backstage instance that you own and can customize.

bash
# Create a new Backstage app
npx @backstage/create-app@latest
 
# When prompted, give it a name
# Name: my-developer-portal

This will create a directory my-developer-portal/ with the full Backstage application. The structure looks like this:

my-developer-portal/
├── app-config.yaml          # Main configuration file
├── packages/
│   ├── app/                 # Frontend (React)
│   └── backend/             # Backend (Node.js)
├── plugins/                 # Custom plugins go here
└── package.json

The most important file for our purposes is app-config.yaml. Almost all Backstage configuration happens here.


Step 2: Configure GitHub Integration

Backstage reads catalog-info.yaml files from your GitHub repositories. To do this, it needs a GitHub token with repository access.

Create a GitHub Personal Access Token

Go to GitHub → Settings → Developer Settings → Personal Access Tokens → Tokens (classic). Create a token with the following scopes:

  • repo (full repository access)
  • read:org (read organization data)
  • read:user

Keep this token — you will need it in the next step.

Update app-config.yaml

Open app-config.yaml and add the GitHub integration block:

yaml
# app-config.yaml
 
app:
  title: My Developer Portal
  baseUrl: http://localhost:3000
 
organization:
  name: Your Company Name
 
backend:
  baseUrl: http://localhost:7007
  listen:
    port: 7007
  database:
    client: better-sqlite3
    connection: ':memory:'
 
integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}
 
auth:
  providers:
    github:
      development:
        clientId: ${GITHUB_CLIENT_ID}
        clientSecret: ${GITHUB_CLIENT_SECRET}
 
catalog:
  providers:
    github:
      defaultOrg:
        organization: your-github-org
        catalogPath: /catalog-info.yaml
        filters:
          branch: main
        schedule:
          frequency: { minutes: 30 }
          timeout: { minutes: 3 }
  rules:
    - allow: [Component, System, API, Resource, Location, Template]

Set Environment Variables

bash
export GITHUB_TOKEN=your-github-token
export GITHUB_CLIENT_ID=your-github-oauth-app-client-id
export GITHUB_CLIENT_SECRET=your-github-oauth-app-secret

For GitHub OAuth (which enables GitHub login in Backstage): go to GitHub → Settings → Developer Settings → OAuth Apps → New OAuth App. Set the callback URL to http://localhost:7007/api/auth/github/handler/frame.


Step 3: Run Backstage Locally

Start the development server to verify everything is working:

bash
cd my-developer-portal
yarn install
yarn dev

This starts both the frontend (port 3000) and backend (port 7007). Open http://localhost:3000 in your browser. You should see the Backstage UI with an empty catalog.


Step 4: Register Your First Service

For Backstage to know about a service, you add a catalog-info.yaml file to the service's repository. This is the fundamental concept of Backstage's Software Catalog — the catalog is distributed across your repositories, not centralized in a database you maintain manually.

catalog-info.yaml Structure

Here is a real-world example for a Node.js API service:

yaml
# catalog-info.yaml (lives in the root of your service repo)
 
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-api
  description: Handles payment processing and subscription management
  annotations:
    github.com/project-slug: your-org/payment-api
    backstage.io/techdocs-ref: dir:.
    backstage.io/kubernetes-id: payment-api
  tags:
    - nodejs
    - api
    - payments
  links:
    - url: https://grafana.yourdomain.com/d/payment-api
      title: Grafana Dashboard
      icon: dashboard
    - url: https://docs.yourdomain.com/payment-api
      title: External Docs
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: billing-platform
  dependsOn:
    - component:postgres-payments
    - component:stripe-integration
  providesApis:
    - payment-api-v1

The key fields:

  • kind: Component (a deployable service), API, System, Resource, or User/Group
  • spec.owner: The team responsible for this service (must match a Group in your catalog)
  • spec.lifecycle: production, experimental, or deprecated
  • spec.dependsOn: Other catalog entities this service depends on (used to build the dependency graph)

Register in Backstage

After adding catalog-info.yaml to your repository, register it in Backstage:

  1. Go to http://localhost:3000/catalog-import
  2. Enter the URL to your catalog-info.yaml: https://github.com/your-org/payment-api/blob/main/catalog-info.yaml
  3. Click Analyze, then Import

If you configured the GitHub auto-discovery in Step 2, Backstage will automatically find catalog-info.yaml files across your organization every 30 minutes — you only need to manually import when you first set up auto-discovery.


Step 5: Add the Kubernetes Plugin

The Kubernetes plugin lets engineers see pod status, deployment health, and recent events for their service directly in Backstage — without needing kubectl access.

Install the Plugin

bash
# Install frontend plugin
cd packages/app
yarn add @backstage/plugin-kubernetes
 
# Install backend plugin
cd ../backend
yarn add @backstage/plugin-kubernetes-backend

Configure the Backend

In packages/backend/src/index.ts, add:

typescript
import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
 
// Inside the main function:
const { router } = await KubernetesBuilder.createBuilder({
  logger,
  config,
  discovery,
  permissions,
}).build();
apiRouter.use('/kubernetes', router);

Add Cluster Configuration

In app-config.yaml:

yaml
kubernetes:
  serviceLocatorMethod:
    type: multiTenant
  clusterLocatorMethods:
    - type: config
      clusters:
        - url: https://your-cluster-api-endpoint
          name: production
          authProvider: serviceAccount
          serviceAccountToken: ${K8S_SERVICE_ACCOUNT_TOKEN}
          caData: ${K8S_CA_DATA}
          skipTLSVerify: false

Create a ServiceAccount in your cluster for Backstage:

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: backstage
  namespace: backstage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backstage-read
rules:
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch"]

For Kubernetes to associate pods with a Backstage component, add this label to your Deployments:

yaml
labels:
  backstage.io/kubernetes-id: payment-api

Step 6: Build the Docker Image

When you are ready to move beyond local development, build a production Docker image:

bash
# Build the backend image
yarn build:backend --config ../../app-config.yaml
 
# Build the Docker image
docker build . -f packages/backend/Dockerfile --tag backstage:1.0.0

The default Dockerfile in packages/backend/Dockerfile is production-ready. It is a multi-stage build that installs only production dependencies.


Step 7: Deploy to Kubernetes

Create a Kubernetes namespace and deploy Backstage.

Kubernetes Manifests

yaml
# backstage-deployment.yaml
 
apiVersion: v1
kind: Namespace
metadata:
  name: backstage
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backstage
  namespace: backstage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backstage
  template:
    metadata:
      labels:
        app: backstage
    spec:
      serviceAccountName: backstage
      containers:
        - name: backstage
          image: your-registry/backstage:1.0.0
          ports:
            - containerPort: 7007
          env:
            - name: GITHUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: backstage-secrets
                  key: GITHUB_TOKEN
            - name: POSTGRES_HOST
              value: "backstage-postgres"
            - name: POSTGRES_PORT
              value: "5432"
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: backstage-secrets
                  key: POSTGRES_USER
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: backstage-secrets
                  key: POSTGRES_PASSWORD
          resources:
            requests:
              cpu: "250m"
              memory: "512Mi"
            limits:
              cpu: "1000m"
              memory: "1Gi"
---
apiVersion: v1
kind: Service
metadata:
  name: backstage
  namespace: backstage
spec:
  selector:
    app: backstage
  ports:
    - port: 80
      targetPort: 7007
  type: ClusterIP

Create Secrets

bash
kubectl create secret generic backstage-secrets \
  --namespace backstage \
  --from-literal=GITHUB_TOKEN=your-token \
  --from-literal=POSTGRES_USER=backstage \
  --from-literal=POSTGRES_PASSWORD=your-password

Apply and Verify

bash
kubectl apply -f backstage-deployment.yaml
 
# Check pod is running
kubectl get pods -n backstage
 
# Follow logs
kubectl logs -n backstage -l app=backstage -f

Step 8: Switch to PostgreSQL for Production

The default in-memory SQLite database loses all data on pod restart. For production, use PostgreSQL.

Update app-config.yaml:

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

You can deploy PostgreSQL using the Bitnami Helm chart:

bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install backstage-postgres bitnami/postgresql \
  --namespace backstage \
  --set auth.username=backstage \
  --set auth.password=your-secure-password \
  --set auth.database=backstage_plugin_catalog

What to Build Next

Once your basic Backstage instance is running, here is the prioritized list of additions that provide the most value:

  1. Software Templates: Create a template for new service creation so engineers spin up repositories with CI/CD, catalog-info.yaml, and TechDocs pre-configured.

  2. TechDocs: Move your service documentation to Markdown files in repositories, rendered inside Backstage. Run mkdocs locally to preview, then configure the TechDocs plugin to build and serve docs.

  3. GitHub Actions Plugin: Show CI/CD pipeline status for each service inside its Backstage page.

  4. Cost Plugin: Connect your cloud cost data so teams can see spending per service directly in the catalog.


Learning More

Platform engineering and Internal Developer Portals are one of the fastest-growing areas in DevOps. Backstage is at the center of it. If you want structured learning on platform engineering concepts and Kubernetes — the foundation you need to manage Backstage well — KodeKloud's platform engineering path is one of the best structured resources available.

For hosting your Backstage instance, DigitalOcean's managed Kubernetes is worth considering — it is significantly cheaper than EKS or GKE for small teams, and Backstage's resource requirements (1 CPU, 1GB RAM) fit comfortably in a small DOKS cluster.


Conclusion

Backstage takes a few hours to set up and a few weeks to populate meaningfully. The investment pays off when your fifth engineer does not have to ask where the payment service lives, who owns it, and why it is throwing errors — because all of that is in the catalog.

The pattern that works: start with your five most critical services, get the basics right (catalog, GitHub integration, Kubernetes plugin), and then let teams populate the catalog themselves. Backstage works best as a team-owned tool, not a top-down mandate.

If your organization is managing more than 10 services, this is one of the highest-leverage infrastructure investments you can make in 2026.

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments