Building Internal Developer Platforms
An Internal Developer Platform (IDP) is a curated set of tools, services, and workflows that reduces cognitive load and accelerates application development. Building an effective IDP requires understanding developer needs, designing appropriate abstractions, and iterating based on feedback.
IDP Architecture
A well-designed IDP consists of several integrated layers working together.
Core Components
The platform stack includes multiple integrated systems:
graph TB
subgraph Developer Interface
A[CLI Tools]
B[Web Portal]
C[IDE Plugins]
end
subgraph Platform Services
D[Application Lifecycle]
E[Infrastructure Provisioning]
F[CI/CD Orchestration]
G[Observability Stack]
end
subgraph Foundation
H[Kubernetes]
I[Cloud Resources]
J[Databases]
K[Message Queues]
end
A --> D
B --> D
C --> D
D --> E
D --> F
D --> G
E --> H
E --> I
F --> H
G --> H
H --> J
H --> K
style D fill:#e1f5ff
style H fill:#fff4e6
Application Lifecycle Management
Centralized application management:
// Platform API for application management
package platform
type Application struct {
Name string
Team string
Runtime string
Replicas int
Resources ResourceRequirements
Database *DatabaseConfig
Cache *CacheConfig
Ingress *IngressConfig
}
type PlatformController struct {
k8sClient kubernetes.Interface
dbProvisioner DatabaseProvisioner
cicdOrchestra CICDOrchestrator
monitoringMgr MonitoringManager
}
func (p *PlatformController) CreateApplication(app Application) error {
// Create namespace
if err := p.createNamespace(app.Name, app.Team); err != nil {
return err
}
// Provision database if requested
if app.Database != nil {
dbEndpoint, err := p.dbProvisioner.Provision(app.Name, app.Database)
if err != nil {
return err
}
app.Database.Endpoint = dbEndpoint
}
// Setup CI/CD pipeline
if err := p.cicdOrchestra.CreatePipeline(app); err != nil {
return err
}
// Configure monitoring
if err := p.monitoringMgr.SetupMonitoring(app); err != nil {
return err
}
// Create deployment manifests
if err := p.createDeployment(app); err != nil {
return err
}
return nil
}
Infrastructure Abstraction
Hide cloud complexity behind simple interfaces:
# High-level platform resource definition
apiVersion: platform.example.com/v1
kind: Application
metadata:
name: order-service
namespace: commerce
spec:
type: api
runtime: python
version: "3.11"
scaling:
min: 3
max: 20
targetCPU: 70
resources:
tier: medium # Platform translates to actual resource values
dependencies:
- name: postgres
type: database
version: "15"
size: large
- name: redis
type: cache
size: medium
- name: orders-queue
type: rabbitmq
size: small
networking:
public: true
domain: orders.example.com
tls: auto
observability:
metrics: true
logging: true
tracing: true
alerts:
- type: error-rate
threshold: 5%
- type: latency-p95
threshold: 500ms
Platform controller translates this into actual infrastructure.
Building the Platform
Constructing an IDP follows a structured approach.
Discovery Phase
Understand developer pain points:
## Developer Interview Questions
### Current Workflow
1. Walk me through deploying a new service from scratch
2. What are the most time-consuming steps?
3. Where do you get blocked waiting for others?
4. What manual steps do you repeat frequently?
### Pain Points
1. What frustrates you most about current tooling?
2. What takes longer than it should?
3. What information is hard to find?
4. What causes the most production incidents?
### Ideal State
1. If you could wave a magic wand, what would change?
2. What capabilities would save the most time?
3. What would make on-call easier?
4. What would you like to stop doing?
Analyze responses to identify patterns:
Common Pain Points Identified:
├── Infrastructure Provisioning: 8/10 teams
│ └── Average wait time: 3-5 days
├── Database Setup: 7/10 teams
│ └── Manual process, often incorrect configuration
├── Monitoring Setup: 9/10 teams
│ └── Each team builds own dashboards
├── Deployment Complexity: 10/10 teams
│ └── Different process for each team
└── Debugging Production Issues: 10/10 teams
└── Logs scattered across systems
MVP Definition
Start with highest-impact features:
# Platform MVP Scope
version: 1.0
features:
- name: One-Click Application Creation
priority: P0
description: Create new service with single command
acceptance:
- Command completes in <5 minutes
- Creates namespace, deployment, service
- Sets up CI/CD pipeline
- Configures basic monitoring
- name: Self-Service Database Provisioning
priority: P0
description: Request database without tickets
acceptance:
- Database ready in <10 minutes
- Connection details automatically injected
- Backups configured automatically
- name: Integrated Observability
priority: P0
description: Automatic dashboards and alerts
acceptance:
- Dashboard created on first deployment
- Standard alerts configured
- Logs automatically aggregated
- name: Simple Deployment
priority: P0
description: Deploy with git push
acceptance:
- Push to main triggers deployment
- Automated testing before deploy
- Rollback capability
deferred:
- Multi-region support
- A/B testing
- Advanced scaling policies
- Custom resource types
Technology Selection
Choose appropriate technologies:
# Platform Technology Stack
infrastructure:
orchestration: kubernetes
version: "1.27"
distribution: eks # or gke, aks
gitops:
tool: argocd
version: "2.8"
ci_cd:
tool: github-actions
runners: self-hosted
observability:
metrics:
tool: prometheus
storage: thanos
retention: 90d
logging:
tool: loki
retention: 30d
tracing:
tool: tempo
retention: 7d
dashboards:
tool: grafana
developer_interface:
cli:
language: go
package_manager: homebrew
portal:
framework: react
backend: golang
api:
protocol: grpc
rest_gateway: true
infrastructure_as_code:
provisioning: terraform
kubernetes_config: kustomize
Platform Capabilities
Key features that make platforms valuable.
Service Templates
Standardized starting points:
# Platform service templates
$ platform templates list
Available Templates:
api-service-go - REST API service in Go
api-service-python - REST API service in Python
api-service-node - REST API service in Node.js
worker-go - Background worker in Go
scheduled-job - Kubernetes CronJob
frontend-react - React frontend application
frontend-static - Static website
$ platform create service payment-api --template api-service-go
Creating service from template api-service-go...
✓ Generated repository structure
✓ Created CI/CD pipeline
✓ Configured deployment manifests
✓ Setup monitoring dashboards
✓ Initialized git repository
Repository: git@github.com:company/payment-api
Documentation: https://platform.example.com/services/payment-api
Template structure:
templates/api-service-go/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handlers/
│ ├── models/
│ └── repository/
├── pkg/
│ └── platform/
│ ├── database.go
│ ├── logging.go
│ ├── metrics.go
│ └── tracing.go
├── k8s/
│ ├── base/
│ └── overlays/
├── .github/
│ └── workflows/
│ ├── ci.yaml
│ └── cd.yaml
├── Dockerfile
├── go.mod
└── README.md
Infrastructure Provisioning
Automated resource creation:
# Platform provisioning service
from platform import database, cache, queue
class InfrastructureProvisioner:
def provision_database(self, app_name: str, config: dict) -> dict:
"""Provision database for application"""
db_instance = database.create(
name=f"{app_name}-db",
engine=config.get('engine', 'postgresql'),
version=config.get('version', '15'),
size=config.get('size', 'medium'),
backup_retention=30,
multi_az=True,
encrypted=True
)
# Create credentials secret
credentials = database.create_credentials(db_instance.id)
self.k8s.create_secret(
namespace=app_name,
name=f"{app_name}-db-credentials",
data=credentials
)
# Setup monitoring
self.monitoring.create_dashboard(
name=f"{app_name}-database",
database_id=db_instance.id
)
return {
'endpoint': db_instance.endpoint,
'port': db_instance.port,
'secret_name': f"{app_name}-db-credentials"
}
def provision_cache(self, app_name: str, config: dict) -> dict:
"""Provision Redis cache"""
cache_instance = cache.create(
name=f"{app_name}-cache",
engine='redis',
version=config.get('version', '7.0'),
size=config.get('size', 'small'),
num_replicas=2
)
return {
'endpoint': cache_instance.endpoint,
'port': cache_instance.port
}
CI/CD Integration
Automated build and deployment:
# Platform-generated CI/CD pipeline
name: Platform CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: |
make test
- name: Build image
run: |
docker build -t ${{ secrets.REGISTRY }}/payment-api:${{ github.sha }} .
- name: Security scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ secrets.REGISTRY }}/payment-api:${{ github.sha }}
severity: HIGH,CRITICAL
- name: Push image
if: github.ref == 'refs/heads/main'
run: |
docker push ${{ secrets.REGISTRY }}/payment-api:${{ github.sha }}
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Update manifest
uses: platform/update-manifest-action@v1
with:
application: payment-api
image: ${{ secrets.REGISTRY }}/payment-api:${{ github.sha }}
environment: production
- name: Notify deployment
uses: platform/notify-action@v1
with:
status: success
application: payment-api
Observability Integration
Automatic monitoring setup:
// Platform observability setup
package observability
func SetupMonitoring(app Application) error {
// Create Grafana dashboard
dashboard := createDashboard(app)
if err := grafana.CreateDashboard(dashboard); err != nil {
return err
}
// Create Prometheus ServiceMonitor
serviceMonitor := &promv1.ServiceMonitor{
ObjectMeta: metav1.ObjectMeta{
Name: app.Name,
Namespace: app.Namespace,
},
Spec: promv1.ServiceMonitorSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": app.Name,
},
},
Endpoints: []promv1.Endpoint{
{
Port: "metrics",
Interval: "30s",
},
},
},
}
// Create alert rules
alerts := createAlertRules(app)
if err := prometheus.CreateRules(alerts); err != nil {
return err
}
// Configure log aggregation
if err := loki.CreateLogStream(app.Name, app.Namespace); err != nil {
return err
}
return nil
}
func createDashboard(app Application) Dashboard {
return Dashboard{
Title: fmt.Sprintf("%s Overview", app.Name),
Panels: []Panel{
{
Title: "Request Rate",
Query: fmt.Sprintf(
`sum(rate(http_requests_total{app="%s"}[5m]))`,
app.Name,
),
},
{
Title: "Error Rate",
Query: fmt.Sprintf(
`sum(rate(http_requests_total{app="%s",status=~"5.."}[5m])) / sum(rate(http_requests_total{app="%s"}[5m]))`,
app.Name, app.Name,
),
},
{
Title: "Response Time (p95)",
Query: fmt.Sprintf(
`histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{app="%s"}[5m])) by (le))`,
app.Name,
),
},
},
}
}
Platform SDK
Provide libraries for common functionality:
# Platform Python SDK
from platform_sdk import Database, Cache, Logging, Metrics, Tracing
# Automatically configured from environment
db = Database()
cache = Cache()
logger = Logging.get_logger(__name__)
metrics = Metrics()
tracer = Tracing.get_tracer(__name__)
@app.route('/api/orders/<order_id>')
@tracer.start_span('get_order')
def get_order(order_id):
timer = metrics.timer('order_retrieval_duration')
timer.start()
try:
# Check cache
cached = cache.get(f'order:{order_id}')
if cached:
metrics.increment('cache_hits')
logger.info('Cache hit', order_id=order_id)
return cached
metrics.increment('cache_misses')
# Query database
order = db.query_one(
'SELECT * FROM orders WHERE id = %s',
[order_id]
)
if not order:
logger.warn('Order not found', order_id=order_id)
metrics.increment('orders_not_found')
return {'error': 'Not found'}, 404
# Cache result
cache.set(f'order:{order_id}', order, ttl=300)
logger.info('Order retrieved', order_id=order_id)
metrics.increment('orders_retrieved')
return order
except Exception as e:
logger.error('Failed to retrieve order',
order_id=order_id,
error=str(e))
metrics.increment('order_retrieval_errors')
raise
finally:
timer.stop()
Platform Evolution
Mature platforms continuously improve.
Feedback Loops
Gather input systematically:
# Platform feedback mechanisms
surveys:
frequency: quarterly
questions:
- How satisfied are you with the platform? (1-10)
- What feature would have the biggest impact?
- What causes the most friction?
- What documentation is missing or unclear?
office_hours:
frequency: weekly
format: open_forum
duration: 1_hour
topics:
- Platform features
- Troubleshooting
- Feature requests
- Best practices
metrics:
track:
- Platform adoption rate
- Time to first deployment
- Developer satisfaction score
- Support ticket volume
- Feature usage statistics
feedback_channels:
- Slack channel: #platform-feedback
- Email: platform-team@example.com
- Issue tracker: github.com/company/platform/issues
Versioning Strategy
Manage platform evolution:
## Platform Versioning
### Semantic Versioning
- Major: Breaking changes requiring migration
- Minor: New features, backward compatible
- Patch: Bug fixes, no feature changes
### Version Support
- Current version: Full support
- Previous version: Security fixes only (6 months)
- Older versions: No support
### Migration Process
1. Announce deprecation 3 months in advance
2. Provide migration guide and tools
3. Offer migration support during transition
4. Monitor adoption of new version
5. Remove deprecated version after support period
Common Pitfalls
Building in Isolation: Creating platform without developer input results in unused features. Continuously engage with platform users.
Over-Engineering: Adding unnecessary features increases complexity. Focus on solving actual pain points first.
Poor Documentation: Complex platforms without documentation frustrate users. Invest equally in docs and code.
Ignoring Feedback: Dismissing user feedback alienates developers. Treat feedback seriously and act on it.
Key Takeaways
- Internal Developer Platforms abstract infrastructure complexity through integrated layers: developer interface, platform services, and foundation resources
- Core capabilities include application lifecycle management, infrastructure provisioning, CI/CD integration, and automatic observability setup
- Build platforms through discovery of pain points, MVP definition focused on high-impact features, and appropriate technology selection
- Provide service templates for quick starts, automated provisioning for resources, generated CI/CD pipelines, and platform SDKs for common functionality
- Evolve platforms through feedback loops including surveys, office hours, metrics tracking, and multiple feedback channels
- Manage evolution with semantic versioning, clear support windows, and structured migration processes
- Avoid building in isolation, over-engineering solutions, neglecting documentation, and ignoring user feedback