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.
- What Backstage Actually Is
- Scaffolding Your First App
- Registering a Service in the Catalog
- Software Templates: the Golden-Path Mechanism
- TechDocs: Docs That Live With the Code
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
npx @backstage/create-app@latestThe 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
cd my-backstage-app
yarn install
yarn dev # Frontend on :3000, backend on :7007By 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)
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.yamlEvery 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)
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:
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-teamSoftware 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.
yarn --cwd packages/app add @backstage/plugin-techdocs
yarn --cwd packages/backend add @backstage/plugin-techdocs-backend1# 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 deploymentEach 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.
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-github-provider1# 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: userIdMatchingUserEntityAnnotationclientId/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
ownerfields or stalelifecyclevalues, 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
- Backstage: Getting Started — the standalone installation walkthrough
- Backstage: GitHub Discovery — the catalog provider config this post uses
- Backstage: TechDocs — builder/generator/publisher configuration in depth
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


