Platform Engineering
9 min readOctober 7, 2026

Backstage: Setting Up Your First Internal Developer Portal

CO
Coding Protocols Team
Platform Engineering
Backstage: Setting Up Your First Internal Developer Portal

Quick answer

Backstage is a React frontend, a Node.js backend, and a plugin system built around one core data model: the Software Catalog. Here's how to actually stand one up — scaffolding the app, registering your first service (manually and via auto-discovery), wiring up TechDocs and GitHub auth, and what running it in production actually costs you in engineering time.

9 min read · Platform Engineering

Backstage is CNCF Graduated, originally built at Spotify, and is the most widely adopted open-source tool for building an internal developer portal. Most teams know the pitch — "one place to find every service, API, and doc" — but the actual mechanics of standing one up are less talked about than the comparison posts that pit it against SaaS alternatives. This is the setup guide: what you actually run, what you actually configure, and what it actually costs you to keep running.


What Backstage Actually Is

Backstage ships as two Node.js processes: a React frontend (packages/app) and a backend (packages/backend) that powers the Software Catalog, Software Templates, authentication, and TechDocs. Everything else — the Kubernetes plugin, the PagerDuty plugin, cost dashboards — is a plugin installed into one or both of those packages.

The one concept that everything else builds on is the Software Catalog: a graph of entities, each with a kind. The kinds you'll actually use:

  • Component — a piece of software: a service, a website, a library
  • API — an interface a Component exposes (REST, GraphQL, gRPC, async)
  • Resource — infrastructure a Component depends on: a database, an S3 bucket, a queue
  • System — a logical grouping of Components, APIs, and Resources that together deliver a capability
  • Domain — a grouping of Systems, usually mapped to a business area

A Component entity can dependsOn a Resource, providesApis or consumesApis an API, and belong to a System — that relationship graph is what makes the catalog a map of your architecture, not just a list of repos.


Scaffolding Your First App

bash
npx @backstage/create-app@latest

The wizard asks for an app name and generates a directory with:

my-backstage-app/
├── app-config.yaml           # Main config — auth, catalog, techdocs, integrations
├── app-config.production.yaml
├── catalog-info.yaml          # Backstage's own entry in its own catalog
├── packages/
│   ├── app/                   # React frontend
│   └── backend/                # Node.js backend — catalog, auth, scaffolder, techdocs
└── package.json
bash
cd my-backstage-app
yarn install
yarn dev    # Frontend on :3000, backend on :7007

By default the backend uses an in-memory SQLite database — fine for evaluating locally, not for anything you intend to keep running. Production installs point backend.database at Postgres.


Registering a Service in the Catalog

There are two ways to get a catalog-info.yaml into Backstage, and they trade off manual control against setup effort.

Static locations (manual, explicit)

yaml
1# app-config.yaml
2catalog:
3  locations:
4    - type: url
5      target: https://github.com/my-org/payments-api/blob/main/catalog-info.yaml
6    - type: url
7      target: https://github.com/my-org/orders-api/blob/main/catalog-info.yaml

Every repo you want in the catalog gets its own line here. Explicit, auditable, and tedious past a handful of services.

GitHub discovery (automatic, org-wide)

yaml
1# app-config.yaml
2catalog:
3  providers:
4    github:
5      providerId:
6        organization: "my-org"
7        catalogPath: "/catalog-info.yaml"
8        filters:
9          visibility: ["public", "internal"]
10          allowArchived: false
11        schedule:
12          frequency: { minutes: 30 }
13          timeout: { minutes: 3 }

This processor scans every repo in my-org on a 30-minute schedule and ingests any catalog-info.yaml it finds at the configured path (wildcards are supported for repos that put it somewhere nonstandard). This is the one that actually scales — once a team adds a catalog-info.yaml to their repo, it shows up in the catalog on the next scan with zero action from the platform team.

A minimal catalog-info.yaml for a service repo:

yaml
1apiVersion: backstage.io/v1alpha1
2kind: Component
3metadata:
4  name: orders-api
5  description: "Order creation and fulfillment service"
6  annotations:
7    backstage.io/techdocs-ref: dir:.
8spec:
9  type: service
10  lifecycle: production
11  owner: group:orders-team

Software Templates: the Golden-Path Mechanism

Templates are what let a developer click "Create," fill in a few fields, and get a new repo with CI, Kubernetes manifests, and a catalog entry already wired up — this is the actual mechanism behind "golden paths." The template.yaml structure (parameters, fetch:template/publish:github/catalog:register steps, and a skeleton directory of parameterized files) is covered in full in Platform Engineering: Building Golden Paths for Developer Self-Service — if you're setting up your first portal, get the catalog working first, then come back to templates once a few real services are registered and you can see which fields teams actually need.


Kubernetes Production Readiness Checklist

The pre-launch checks we run before calling a cluster production-ready — probes, resources, RBAC, upgrades, and backups. Plain Markdown you can commit to your repo.

Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.

TechDocs: Docs That Live With the Code

TechDocs renders Markdown committed alongside the service code (via MkDocs) directly into the catalog entity's page, so documentation doesn't drift into a separate wiki nobody updates.

bash
yarn --cwd packages/app add @backstage/plugin-techdocs
yarn --cwd packages/backend add @backstage/plugin-techdocs-backend
yaml
1# app-config.yaml
2techdocs:
3  builder: "local"          # or "external" if a CI job pre-builds docs
4  generator:
5    runIn: "docker"         # runs MkDocs inside a Docker container — no local Python/MkDocs install needed
6  publisher:
7    type: "local"           # or awsS3/googleGcs for a real deployment

Each service repo needs an mkdocs.yml at its root and its docs under docs/, plus the backstage.io/techdocs-ref: dir:. annotation shown in the catalog-info.yaml above, which tells Backstage where to find the source. builder: local with generator.runIn: docker is the right starting point — move to an external builder (docs generated in CI, published to S3/GCS) once you have enough services that generating every doc site on-demand in the Backstage backend becomes a bottleneck.


Authentication: GitHub OAuth

Backstage needs an identity provider — GitHub is the simplest to wire up if your org already lives there.

bash
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-github-provider
yaml
1# app-config.yaml
2auth:
3  environment: development
4  providers:
5    github:
6      development:
7        clientId: ${AUTH_GITHUB_CLIENT_ID}
8        clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
9        signIn:
10          resolvers:
11            - resolver: userIdMatchingUserEntityAnnotation

clientId/clientSecret come from a GitHub OAuth App you register under your org (callback URL pointing at your Backstage backend). The signIn.resolvers field is required — it's the rule Backstage uses to match the GitHub identity that just signed in to a User entity already in the catalog (typically synced from your GitHub org via the same discovery mechanism used for services).


What Running Backstage Actually Takes

This is the part the "Backstage is free and open-source" framing skips: Backstage is a piece of software you now own. In practice that means:

  • A dedicated owner. Someone (usually a platform team, not an individual) maintains the catalog's data quality, keeps templates current as your stack changes, and owns plugin upgrades.
  • A real backend. Postgres in production, not the default SQLite — and a deployment (container image, Helm chart or similar) you patch and monitor like any other service.
  • Plugin maintenance. Each plugin you add (Kubernetes, cost tooling, PagerDuty, CI status) is a dependency that needs upgrading in step with Backstage core releases, which ship frequently.
  • Catalog hygiene as an ongoing job. Auto-discovery solves ingestion, not quality — someone still needs to chase down services with missing owner fields or stale lifecycle values, or the catalog degrades into noise nobody trusts.

This is the real tradeoff against a hosted IDP like Port: Backstage gives you full control and no vendor lock-in, in exchange for carrying all of the above yourself. See Backstage vs Port for the full field-by-field comparison if you're still deciding between the two.


Frequently Asked Questions

Do I need Kubernetes to run Backstage?

No — Backstage itself is just two Node.js processes and a Postgres database; it has no dependency on Kubernetes to function. Most platform teams do run it on Kubernetes because that's where the rest of their infrastructure lives (and because the Kubernetes plugin, which shows live pod/deployment status on catalog pages, is one of the most commonly installed plugins), but a small team can run Backstage on a single VM or a managed container service just as validly.

What's the difference between the Software Catalog and a service registry like Consul?

A service registry like Consul tracks runtime state — which instances are alive, right now, for service discovery and routing. The Software Catalog tracks ownership, architecture, and documentation metadata — who owns this service, what it depends on, where its docs live, what lifecycle stage it's in. They solve different problems and commonly coexist: Consul (or your cloud's native service discovery) handles traffic; Backstage handles "what is this, and who do I ask about it."

Can I use Backstage without Software Templates?

Yes. Many teams adopt Backstage purely for the Software Catalog and TechDocs first — getting every service discoverable with an owner and docs attached is valuable on its own — and add Software Templates later once they understand which golden paths their teams actually need. Templates without a populated catalog to register into are the less valuable half of Backstage; catalog-first adoption is the more common, lower-risk sequencing.

Why does my catalog entity show as orphaned or missing its owner?

"Orphaned" means the entity's relations no longer resolve — usually because the owner field references a Group that doesn't exist in the catalog (common if you haven't set up org/team discovery yet, only service discovery). Register your GitHub teams as Group entities (via the same GitHub discovery provider, configured for teams) before requiring an owner annotation on every service, or entities will orphan as soon as you enforce it.


For the Software Template mechanics that turn the catalog into a self-service golden path, see Platform Engineering: Building Golden Paths for Developer Self-Service. For the broader IDP context — why platforms exist and how adoption actually happens — see Platform Engineering: Building an Internal Developer Platform and What Is Platform Engineering?.

Standing up an internal developer portal or deciding whether Backstage is the right foundation for your platform team? Talk to us at Coding Protocols — we help platform teams design catalogs, templates, and golden paths that developers actually use.

Official References

Was this article helpful?

Be the first to rate this article

Related Topics

Backstage
Platform Engineering
Internal Developer Platform
Kubernetes
IDP

Found this useful? Share it.

Practice this

Related tools

Read Next

Want this running in production, not just on paper?

We're a hands-on DevOps consultancy — Kubernetes, CI/CD, and cloud infrastructure.

Explore Our Services