Platform Engineering — Building an Internal Developer Platform That Engineers Actually Use
Advertisement
Introduction
An Internal Developer Platform (IDP) enables self-service infrastructure. Bad IDPs force engineers into rigid processes. Good IDPs make the right path the easy path. This post covers IDP philosophy, Backstage setup, golden paths, and adoption metrics.
What an IDP Is (Not Just Kubernetes)
An IDP is not:
- A Kubernetes cluster (that's infrastructure)
- A CI/CD pipeline (that's part of it)
- A container registry (also part of it)
An IDP is a product for engineers, providing:
- Service catalog: discoverable services and their owners
- Scaffolding templates: create new services in minutes
- Paved roads: documented, proven architectures
- Internal APIs: standardized authentication, databases, queues
- Developer portal: unified view of all services
Example IDP flow:
- Engineer discovers service template in Backstage
- Engineer generates new service from template (auto-creates Git repo, CI/CD, monitoring)
- Engineer deploys via
git push(no manual infra work) - Platform team monitors and evolves the paved road
Golden Paths vs Golden Cages
Golden path: The recommended way to do something, but other options exist. Engineers can deviate with justification.
Golden cage: The only way to do something. Forces all engineers onto one path, frustrating those with different needs.
# Good IDP: Python microservice template
# BUT engineers can also use Go, Node.js, Rust if justified
templates:
- name: python-microservice
description: Recommended template
inputs:
- name: service_name
- name: database_type
type: select
options: [postgres, mongodb, dynamodb]
- name: go-service # Alternative path
description: For performance-critical services
- name: rust-service # Another option
description: For crypto or embedded workBackstage as Developer Portal
Backstage is an open-source developer portal. It provides:
- Software catalog (YAML registry of services)
- Scaffolder (template-based service generation)
- TechDocs (documentation site generator)
- Plugins (integrations with GitHub, Datadog, PagerDuty)
# catalog/services.yaml - Register services in Backstage
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: order-service
description: Processes customer orders
tags:
- microservice
- payment
spec:
type: service
owner: team-ecommerce
lifecycle: production
providesApis:
- order-api
consumesApis:
- payment-api
- inventory-api
dependsOn:
- resource:postgres-main
- resource:redis-cache
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: order-api
spec:
type: openapi
owner: team-ecommerce
definition:
$text: https://github.com/example/order-service/blob/main/openapi.yamlSelf-Service Infrastructure Templates
Templates auto-generate boilerplate. New service takes 5 minutes, not days.
# scaffolder-template.yaml - Create new Node.js service
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: create-nodejs-service
title: Create Node.js Microservice
description: Generate a new Node.js service with monitoring
spec:
owner: platform-team
type: service
parameters:
- title: Service Details
required: [serviceName, teamName]
properties:
serviceName:
type: string
title: Service Name
description: e.g., payment-service
teamName:
type: string
title: Team Name
enum:
- team-ecommerce
- team-payments
- team-platform
database:
type: string
title: Database Type
enum:
- postgres
- dynamodb
- none
steps:
- id: fetch
name: Fetch template
action: fetch:template
input:
url: ./template
targetPath: ./template-out
values:
serviceName: ${{ parameters.serviceName }}
teamName: ${{ parameters.teamName }}
- id: publish
name: Publish to GitHub
action: publish:github
input:
allowedHosts: ['github.com']
description: Generated ${{ parameters.serviceName }}
repoUrl: github.com?owner=example&repo=${{ parameters.serviceName }}
- id: register
name: Register in catalog
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'Paved Road for New Services
Document the recommended path. Don't hide alternatives, but make the standard path obvious.
# Paved Road: Node.js Microservice
## Fastest way to production (5 min setup, 30 min first deploy)
### 1. Generate service from template
Use Backstage template: [Create Node.js Microservice]
### 2. Deploy to staging
```bash
git push origin main # CI/CD auto-deploys to staging3. Run end-to-end tests
npm run test:e2e4. Deploy to production
git tag v1.0.0
git push origin --tagsMonitoring is automatic
- Logs: CloudWatch
- Metrics: Prometheus
- Traces: Jaeger
- Alerts: PagerDuty
No configuration needed. Default alerting rules applied.
Off-ramp alternatives
- Need Rust? See [Paved Road: Rust Service]
- Custom database? Requires platform review
- Serverless? Use [Lambda template]
Common questions
- How do I add a new dependency? See [Dependency Management]
- How do I run migrations? See [Database Migrations]
## Platform Team as Product Team
Treat platform as a product. Measure adoption, feedback, and satisfaction.
```typescript
// platform-metrics.ts - Track IDP health
import { metrics } from '@opentelemetry/api';
const meter = metrics.getMeter('platform-team');
// New services created this month
export const newServicesCreated = meter.createCounter('platform.new_services_created', {
description: 'New services created via scaffolder',
});
// Engineer satisfaction (quarterly survey)
export const engineerSatisfaction = meter.createGauge('platform.engineer_satisfaction', {
description: 'Net promoter score for platform',
});
// Time to first deploy
export const timeToFirstDeploy = meter.createHistogram('platform.time_to_first_deploy', {
description: 'Time from template generation to first production deploy',
unit: 'minutes',
});
// Template usage
export const templateUsage = meter.createCounter('platform.template_usage', {
description: 'How often each template is used',
});Measuring Platform Adoption and Developer Satisfaction
# quarterly-survey.yaml - Measure adoption
questions:
- "How easy is it to deploy a new service? (1-10)"
- "How much time do you spend on toil? (hours/week)"
- "Would you recommend the platform to a new hire? (yes/no)"
metrics:
service_adoption: % of new services using templates
template_usage: % of teams using paved roads
time_to_deploy: hours from code commit to production
engineer_satisfaction: NPS score
targets:
service_adoption: > 90%
template_usage: > 80%
time_to_deploy: < 2 hours
engineer_satisfaction: > 50 NPSIDP Anti-Patterns
Anti-pattern 1: Golden cage
- Platform enforces one language, one database, one cloud provider
- Engineers with different needs are blocked
- Fix: Allow off-ramp; make paved road obvious
Anti-pattern 2: Abandoned platform
- Platform team builds IDP, then leaves for other projects
- No one maintains templates or updates docs
- Fix: Assign platform team for 2+ years; make it a career
Anti-pattern 3: Over-engineering
- Platform builds every possible feature upfront
- 90% unused; complexity scares off users
- Fix: Start with catalog + one template; iterate based on feedback
Anti-pattern 4: Ineffective communication
- Great platform exists, but engineers don't know about it
- Low adoption due to visibility
- Fix: Monthly demos, office hours, Slack channel
Checklist
- Start with software catalog (register services in YAML)
- Build one paved road template first (not 20)
- Measure adoption: % of new services using templates
- Gather feedback: quarterly surveys from engineers
- Provide off-ramps: document alternatives (Rust, serverless, etc.)
- Assign dedicated platform team (2+ engineers)
- Use Backstage or similar portal
- Document paved road clearly (show happy path first)
- Monitor template satisfaction (which templates used most?)
- Iterate: monthly updates based on engineer feedback
Conclusion
Platform engineering is about removing friction for engineers. Good IDPs make the right path the easy path. Start small (catalog + one template), gather feedback, and iterate. Bad IDPs add bureaucracy; good IDPs enable autonomy. Your IDP's success is measured in engineer satisfaction and adoption, not in features built.
Advertisement