Internal developer portals have emerged as the central hub for engineering organizations. They aggregate documentation, service catalogs, and tooling into a single interface. Done well, they accelerate development. Done poorly, they become another abandoned wiki.
Here’s how to build developer portals that work.
What Developer Portals Provide
The Developer Experience Problem
Without a portal:
Find API docs → Search wiki, Confluence, GitHub, Slack
Discover services → Ask around, grep code, check Kubernetes
Create new service → Copy from somewhere, miss half the setup
Understand ownership → Check git blame, hope it's current
With a portal:
Find API docs → Portal → Service → API Documentation
Discover services → Portal → Service Catalog → Browse/Search
Create new service → Portal → Templates → Scaffold with best practices
Understand ownership → Portal → Service → Team, On-call, Runbooks
Core Capabilities
service_catalog:
- Inventory of all services
- Ownership information
- Dependencies and relationships
- Health status
- Links to resources
documentation:
- Technical docs (TechDocs)
- API specifications
- Runbooks
- Architecture decisions
templates:
- New service scaffolding
- Infrastructure provisioning
- Common patterns
search:
- Unified search across all content
- Code, docs, services, people
integrations:
- CI/CD status
- Monitoring dashboards
- On-call schedules
- Security scans
Backstage
What Is Backstage
Backstage is an open-source developer portal framework created by Spotify:
┌─────────────────────────────────────────────────────────────────┐
│ Backstage │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ Service Catalog │ │ TechDocs │ │ Templates │ │
│ └─────────────────┘ └─────────────────┘ └────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ Search │ │ Kubernetes │ │ CI/CD │ │
│ └─────────────────┘ └─────────────────┘ └────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Plugin System ││
│ │ (100+ community plugins, custom plugins) ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
Getting Started
# Create Backstage app
npx @backstage/create-app
# Start development
cd my-backstage-app
yarn dev
Service Catalog
Define services in YAML:
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: orders-service
description: Handles order processing
annotations:
github.com/project-slug: myorg/orders-service
backstage.io/techdocs-ref: dir:.
tags:
- java
- api
spec:
type: service
lifecycle: production
owner: orders-team
providesApis:
- orders-api
consumesApis:
- payments-api
- inventory-api
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: orders-api
description: Orders REST API
spec:
type: openapi
lifecycle: production
owner: orders-team
definition:
$text: ./openapi.yaml
TechDocs
Documentation as code:
# mkdocs.yml in service repo
site_name: Orders Service
nav:
- Home: index.md
- Architecture: architecture.md
- API Reference: api.md
- Runbooks:
- Incident Response: runbooks/incidents.md
- Deployment: runbooks/deployment.md
plugins:
- techdocs-core
# docs/index.md
# Orders Service
The orders service handles order creation, management, and fulfillment.
## Quick Links
- [API Documentation](api.md)
- [Architecture](architecture.md)
- [Runbooks](runbooks/incidents.md)
Software Templates
Scaffold new services:
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: java-service
title: Java Microservice
description: Create a new Java microservice with Spring Boot
spec:
owner: platform-team
type: service
parameters:
- title: Service Details
required:
- name
- description
- owner
properties:
name:
title: Name
type: string
description:
title: Description
type: string
owner:
title: Owner
type: string
ui:field: OwnerPicker
- title: Repository
required:
- repoUrl
properties:
repoUrl:
title: Repository Location
type: string
ui:field: RepoUrlPicker
steps:
- id: fetch
name: Fetch Template
action: fetch:template
input:
url: ./skeleton
values:
name: ${{ parameters.name }}
description: ${{ parameters.description }}
- id: publish
name: Publish to GitHub
action: publish:github
input:
repoUrl: ${{ parameters.repoUrl }}
- id: register
name: Register Component
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'
output:
links:
- title: Repository
url: ${{ steps.publish.output.remoteUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps.register.output.entityRef }}
Building Your Portal
Start with Catalog
The service catalog is foundational:
rollout_phases:
phase_1:
- Import existing services
- Basic ownership data
- Links to repos and dashboards
phase_2:
- Dependency mapping
- API documentation
- Team information
phase_3:
- TechDocs integration
- Templates for new services
- CI/CD integration
Auto-Discovery
Don’t manually maintain everything:
discovery_sources:
github:
- Scan repos for catalog-info.yaml
- Import automatically
kubernetes:
- Discover deployed services
- Import metadata from labels
terraform:
- Import infrastructure components
- Link to Terraform modules
Integrations
Connect to existing tools:
integrations:
source_control:
- GitHub/GitLab
- Pull request status
- Code owners
ci_cd:
- GitHub Actions
- Jenkins
- ArgoCD deployment status
monitoring:
- Prometheus/Grafana
- PagerDuty on-call
- Datadog dashboards
security:
- Vulnerability scans
- Dependency audits
- Security scores
Making It Successful
Adoption Strategies
adoption_tactics:
make_it_useful:
- Solve real pain points first
- Integrate with daily workflows
- Provide value before requiring effort
make_it_easy:
- Auto-populate what you can
- Templates for common patterns
- Search that actually works
make_it_official:
- Leadership support
- Require for new services
- Migrate docs from wiki
iterate:
- Start simple
- Add features based on feedback
- Measure adoption
Measuring Success
metrics:
adoption:
- Percentage of services cataloged
- Active users per week
- Template usage
efficiency:
- Time to create new service
- Time to find documentation
- Onboarding time reduction
satisfaction:
- Developer NPS
- Feature requests
- Support tickets
Common Pitfalls
avoid:
empty_catalog:
problem: Portal exists but nothing in it
solution: Auto-discovery, seed with existing data
stale_data:
problem: Information becomes outdated
solution: Generate from source, validate regularly
too_complex:
problem: Overwhelming number of features
solution: Start simple, add based on need
no_ownership:
problem: Portal becomes orphaned
solution: Dedicated team, clear roadmap
Customization
Custom Plugins
Extend Backstage with plugins:
// plugins/my-plugin/src/plugin.ts
import { createPlugin, createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
id: 'my-plugin',
});
export const myPlugin = createPlugin({
id: 'my-plugin',
routes: {
root: rootRouteRef,
},
});
export const MyPluginPage = myPlugin.provide(
createRoutableExtension({
name: 'MyPluginPage',
component: () => import('./components/MyPluginPage'),
mountPoint: rootRouteRef,
}),
);
Entity Cards
Add information to service pages:
// Display custom metrics on service page
export const ServiceMetricsCard = () => {
const { entity } = useEntity();
return (
<InfoCard title="Metrics">
<Typography>
Requests/sec: {metrics.requestsPerSecond}
</Typography>
<Typography>
Error rate: {metrics.errorRate}%
</Typography>
</InfoCard>
);
};
Key Takeaways
- Developer portals centralize service discovery, documentation, and tooling
- Backstage is the leading open-source framework; extensible via plugins
- Start with the service catalog; it’s foundational for everything else
- Auto-discover services from GitHub, Kubernetes, and other sources
- TechDocs keeps documentation with code; builds automatically
- Software templates standardize service creation with best practices
- Integration with CI/CD, monitoring, and security provides unified view
- Measure adoption, efficiency, and satisfaction
- Avoid empty catalogs and stale data; automate population
- Dedicated ownership ensures the portal stays valuable
A developer portal is only useful if developers use it. Focus on solving real problems and making it the easiest path to get things done.