Why Your Team Needs an Internal Developer Portal
Picture this: a new developer joins your team. On day one, they need to find the API docs for the payment service, figure out which team owns the notification microservice, locate the CI/CD pipeline for the frontend, and understand the deployment process. Without a central place for all this, they spend days pinging people on Slack and digging through scattered wikis.
That’s the exact problem an Internal Developer Portal (IDP) exists to solve. In teams running more than a handful of services, poor discoverability quietly kills productivity — a developer spending 30 minutes tracking down an API owner, multiplied across 20 people, adds up to real engineering hours lost every week.
Backstage is an open-source framework built by Spotify and donated to the CNCF. It gives developers one place to browse the service catalog, read documentation, trigger pipelines, check infrastructure status, and onboard to new services — without bouncing between six different tools.
This guide focuses specifically on getting Backstage running locally, wiring up your first catalog entries, and setting up basic monitoring so you know the portal stays healthy. No fluff about why platform engineering matters — just the setup.
Installation: Spinning Up Backstage Locally
Before anything else, make sure you have these installed:
- Node.js 18+ (check with
node -v) - Yarn 1.22+ (check with
yarn -v) - Git
- Docker (optional, but you’ll want it for PostgreSQL later)
Create a New Backstage App
Backstage ships a CLI scaffolding tool. Run this in your terminal:
npx @backstage/create-app@latest
You’ll be prompted for an app name — something like my-developer-portal works fine. The CLI scaffolds a monorepo with two packages: packages/app (the React frontend) and packages/backend (the Node.js backend). Expect it to pull down around 800MB of dependencies.
cd my-developer-portal
yarn install
yarn dev
Give it a minute or two, then open http://localhost:3000. You’ll see the Backstage home screen with a sample catalog already loaded.
Understanding the Project Structure
Here’s what actually matters in the scaffolded project:
my-developer-portal/
├── app-config.yaml # Main config file
├── app-config.local.yaml # Local overrides (gitignored)
├── packages/
│ ├── app/ # Frontend (React)
│ └── backend/ # Backend (Node.js + Express)
└── catalog-info.yaml # Your first catalog entry (sample)
Almost all configuration lives in app-config.yaml — database connections, integrations, auth providers, and catalog locations. You’ll be editing this file a lot.
Configuration: Setting Up the Service Catalog
The service catalog is Backstage’s core feature. Every microservice, library, website, or API your team owns becomes a component in the catalog. Here’s how to register a real service.
Write a catalog-info.yaml for Your Service
Inside your actual service repository (not the Backstage repo), create a catalog-info.yaml at the root:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-service
description: Handles all payment processing for checkout flow
annotations:
github.com/project-slug: my-org/payment-service
backstage.io/techdocs-ref: dir:.
tags:
- nodejs
- payments
- api
spec:
type: service
lifecycle: production
owner: payments-team
system: checkout
providesApis:
- payment-api
This file tells Backstage everything it needs: who owns the service, what lifecycle stage it’s in (production, experimental, or deprecated), which APIs it exposes, and where the docs live.
Register the Component in Backstage
Open your portal at http://localhost:3000, go to Catalog → Register Existing Component, and paste the raw GitHub URL to your catalog-info.yaml:
https://github.com/my-org/payment-service/blob/main/catalog-info.yaml
Backstage imports the component immediately. It shows up in the catalog within seconds.
Connect GitHub Integration (for Auto-Discovery)
Manually registering every service doesn’t scale past about 10 repos. Backstage can auto-discover catalog-info.yaml files across your entire GitHub org. Edit app-config.yaml:
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
catalog:
providers:
github:
my-org:
organization: 'my-org'
catalogPath: '/catalog-info.yaml'
filters:
branch: 'main'
schedule:
frequency: { minutes: 30 }
timeout: { minutes: 3 }
Set the environment variable before starting:
export GITHUB_TOKEN=ghp_your_personal_access_token
yarn dev
Backstage will now crawl your GitHub org every 30 minutes, automatically picking up any repo with a catalog-info.yaml on the main branch. Your token needs repo and read:org scopes — without both, you’ll hit 403 errors during discovery.
Enable TechDocs (In-Portal Documentation)
TechDocs lets teams write docs in Markdown alongside their code and read them directly inside Backstage — no external wiki required. First, install the CLI tool:
pip install mkdocs-techdocs-core
In your service repo, create a mkdocs.yml:
site_name: Payment Service
docs_dir: docs
nav:
- Home: index.md
- API Reference: api.md
- Runbooks: runbooks.md
Create a docs/index.md with your content. Open the service in Backstage and you’ll see a Docs tab rendering those Markdown files as a proper documentation site. Teams that adopt this pattern typically see their Slack “where are the docs?” questions drop noticeably within a few weeks.
Switch from SQLite to PostgreSQL (Production-Ready)
By default, Backstage uses an in-memory SQLite database. It resets on every restart, which is fine for local testing but useless for anything beyond that. Switch to PostgreSQL:
docker run -d \
--name backstage-db \
-e POSTGRES_USER=backstage \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=backstage \
-p 5432:5432 \
postgres:15
Update app-config.yaml:
backend:
database:
client: pg
connection:
host: localhost
port: 5432
user: backstage
password: secret
database: backstage
Install the PostgreSQL client for the backend:
yarn --cwd packages/backend add pg
Verification & Monitoring: Keeping the Portal Healthy
Deploying Backstage and walking away is how you end up with a stale portal nobody trusts. Here’s how to verify it’s working and catch problems early.
Check the Catalog API Directly
Backstage exposes a REST API you can query to confirm catalog ingestion is working:
# List all registered components
curl http://localhost:7007/api/catalog/entities?filter=kind=Component | jq '.[].metadata.name'
# Check a specific component
curl http://localhost:7007/api/catalog/entities/by-name/component/default/payment-service | jq '.metadata'
Your service showing up here means the catalog backend ingested it correctly. If it’s missing, check whether the GitHub token has the right scopes.
Monitor Catalog Refresh Logs
When GitHub auto-discovery runs, Backstage logs each refresh cycle. Watch the backend logs for problems:
yarn dev 2>&1 | grep -E '(catalog|error|warn)' | tail -f
Healthy output looks like [catalog] Processed 42 entities. Repeated 403 errors mean your token expired or is missing the repo or read:org scope. Fix the token, restart, and the next 30-minute refresh cycle will pick it up.
Health Check Endpoint
The Backstage backend exposes a health endpoint you can wire into any uptime monitor:
curl http://localhost:7007/healthcheck
# Expected response: {"status":"ok"}
Deploying to Kubernetes? Use this as your readiness probe:
readinessProbe:
httpGet:
path: /healthcheck
port: 7007
initialDelaySeconds: 10
periodSeconds: 15
Verify TechDocs Rendering
After registering a component with TechDocs enabled, click the Docs tab. A blank page or “Documentation not found” means something went wrong during the build. Debug it locally:
cd your-service-repo
techdocs-cli generate --no-docker --verbose
techdocs-cli serve
This builds and previews the docs locally. Most failures come down to a missing mkdocs.yml, a wrong docs_dir path, or a broken Markdown file. Fix it here before it surfaces in the portal.
Track Catalog Coverage
Catalog coverage — the percentage of your real services that have a catalog-info.yaml — is the metric that actually tells you whether the portal is useful. Check it with the GitHub CLI:
# Count repos in your org
gh repo list my-org --limit 200 --json name | jq length
# Count repos with catalog-info.yaml
gh search code 'catalog-info.yaml' --owner my-org --json repository | jq '[.[].repository.name] | unique | length'
Target 80%+ coverage. For teams below that threshold, the fastest way to close the gap is adding catalog entries to your sprint definition-of-done — every new service ships with a catalog-info.yaml on day one.
Next Steps After the Basics
Once your catalog is populated and docs are rendering, several high-value features are worth tackling next:
- Software Templates — Let developers scaffold new services directly in the portal, with your team’s conventions and security defaults baked in from the start
- Auth integration — Connect GitHub SSO or your company’s identity provider so developers log in with existing credentials instead of managing a separate Backstage account
- Custom plugins — Backstage has a plugin ecosystem covering Kubernetes, PagerDuty, Grafana, and Argo CD, surfacing relevant data directly on each service’s catalog page
- Scorecards — Define maturity standards (has docs, has runbook, passes security scan) and track which services meet them across the org
Ten well-documented services in the catalog will already save your team meaningful time on Slack questions and context-switching. The portal gets more valuable as coverage grows — but it needs to be useful on day one to get adoption, so prioritize quality over quantity when you’re getting started.

