Skip to main content
โšก Calmops

Platform Engineering: Building Internal Developer Platforms

Introduction

As organizations scale, developers face increasing complexity: multiple deployment targets, varied authentication systems, different databases, and countless internal tools. This fragmentation slows productivity and creates cognitive load that detracts from actual product development.

Platform Engineering addresses this challenge by creating Internal Developer Platforms (IDPs)โ€”self-service portals that provide developers with everything they need to build, deploy, and operate software efficiently. By 2026, Gartner predicts 80% of enterprises will have implemented platform engineering teams.


What Is Platform Engineering?

The Basic Concept

Platform engineering is the discipline of building and operating internal platforms that provide self-service capabilities to software development teams. These platforms abstract away infrastructure complexity and provide standardized paths to production.

Key Terms

  • Internal Developer Platform (IDP): A self-service platform for developers
  • Golden Paths: Opinionated, pre-configured paths to production
  • Developer Experience (DX): The ease with which developers can accomplish tasks
  • Self-Service: Ability to provision resources without manual intervention
  • Paved Roads: Streamlined, well-supported workflows
  • Backstage: An open-source developer portal (by Spotify)

Why Platform Engineering Matters in 2025-2026

Metric Without Platform With Platform
Time to First Deployment Days-Weeks Minutes-Hours
Onboarding Time Weeks Days
Deployment Frequency Weekly Multiple/day
Failed Deployments 15-20% <5%
Developer Satisfaction 60% 85%+

Architecture

Platform Components

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  Internal Developer Portal                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚ Service     โ”‚  โ”‚ Environment โ”‚  โ”‚ Documentation           โ”‚โ”‚
โ”‚  โ”‚ Catalog     โ”‚  โ”‚ Provisioningโ”‚  โ”‚ & APIs                  โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      Platform Backend                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚ GitOps     โ”‚  โ”‚ IaC         โ”‚  โ”‚ Service Mesh            โ”‚โ”‚
โ”‚  โ”‚ Controller โ”‚  โ”‚ Engine      โ”‚  โ”‚ & Discovery            โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     Infrastructure                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚ Kubernetes  โ”‚  โ”‚ Databases   โ”‚  โ”‚ Cloud Resources         โ”‚โ”‚
โ”‚  โ”‚ Clusters    โ”‚  โ”‚ (SQL/NoSQL) โ”‚  โ”‚ (AWS/GCP/Azure)         โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Building an Internal Developer Platform

Step 1: Assess Current State

# assessment.yaml
infrastructure:
  cloud_providers:
    - name: AWS
      services_used:
        - EKS
        - RDS
        - S3
        - Lambda
      monthly_cost: "$45,000"
  
  kubernetes:
    clusters: 5
    namespaces: 50
    workloads: 200

developer_survey:
  pain_points:
    - "Slow provisioning (2-3 days)"
    - "No clear deployment path"
    - "Inconsistent security configs"
    - "Hard to find documentation"
  
  requests:
    - "Self-service database provisioning"
    - "One-click deploy to staging"
    - "Unified authentication"
    - "Service catalog"

Step 2: Define Golden Paths

# golden-paths/application-template.yaml
apiVersion: platform.example.com/v1
kind: Application
metadata:
  name: my-service
  namespace: default
spec:
  language: go
  framework: gin
  
  infrastructure:
    database:
      type: postgresql
      version: "15"
      tier: small
    
    cache:
      type: redis
      tier: small
    
    queue:
      type: rabbitmq
  
  deployment:
    replicas: 2
    resources:
      cpu: "500m"
      memory: "512Mi"
    autoscaling:
      enabled: true
      min_replicas: 2
      max_replicas: 10
  
  observability:
    logging: true
    metrics: true
    tracing: true
    alert_channels:
      - slack
      - pagerduty

Step 3: Set Up Backstage

# app-config.yaml
app:
  title: My Company Developer Portal
  baseUrl: https://developer.internal.example.com

organization:
  name: My Company

backend:
  baseUrl: https://developer.internal.example.com
  cors:
    origin: https://developer.internal.example.com
  
database:
  client: better-sqlite3
  connection: ":memory:"

catalog:
  locations:
    - type: url
      target: https://github.com/org/backstage-catalog-info/blob/main/catalog.yaml
    
    - type: directory
      target: ./catalog

proxy:
  '/labrador':
    target: https://labrador.example.com
    changeOrigin: true

integrations:
  github:
    - token: ${GITHUB_TOKEN}
      apiBaseUrl: https://api.github.com
      
  jenkins:
    - baseUrl: https://jenkins.internal.example.com
      username: ${JENKINS_USER}
      token: ${JENKINS_TOKEN}

Step 4: Define Components

# catalog-info.yaml (in each service repo)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-service
  description: Payment processing service
  tags:
    - go
    - microservices
    - payments
  links:
    - title: Documentation
      url: https://docs.internal.example.com/payment-service
    - title: API Docs
      url: https://api.internal.example.com/payment-service/swagger
    - title: Grafana
      url: https://grafana.internal.example.com/d/payment-service

spec:
  type: service
  lifecycle: production
  owner: platform-team
  system: payments
  
  providesApis:
    - payment-api
  
  consumesApis:
    - user-api
    - notification-api

  dependsOn:
    - resource:payment-db
    - resource:payment-redis

---
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: payment-db
spec:
  type: database
  owner: platform-team
  system: payments

Self-Service Capabilities

1. Environment Provisioning

// environment-provisioner.ts
import { KubernetesClient } from './kubernetes';
import { DatabaseClient } from './database';

interface EnvironmentRequest {
  name: string;
  namespace: string;
  services: string[];
  databases: DatabaseConfig[];
  resources: ResourceSpec;
}

class EnvironmentProvisioner {
  async provisionEnvironment(request: EnvironmentRequest): Promise<Environment> {
    // 1. Create Kubernetes namespace
    await this.k8s.createNamespace(request.namespace);
    
    // 2. Deploy base services
    for (const service of request.services) {
      await this.k8s.deployService({
        name: service,
        namespace: request.namespace,
        image: `registry.internal/${service}:latest`,
        replicas: request.resources.replicas,
      });
    }
    
    // 3. Provision databases
    for (const db of request.databases) {
      await this.db.provision({
        ...db,
        namespace: request.namespace,
      });
    }
    
    // 4. Configure networking
    await this.k8s.createIngress({
      name: `${request.namespace}-ingress`,
      namespace: request.namespace,
      services: request.services,
    });
    
    // 5. Set up monitoring
    await this.monitoring.configure({
      namespace: request.namespace,
      services: request.services,
    });
    
    return new Environment(request.name, request.namespace);
  }
}

2. Service Creation

# Developer uses platform CLI
platform create service payment-service \
  --language go \
  --framework gin \
  --db postgresql \
  --add-ci \
  --add-cd

# This creates:
# - GitHub repository with template
# - CI/CD pipeline
# - Kubernetes manifests
# - Database credentials (sealed secrets)
# - Monitoring configuration
# - Documentation template

Best Practices

1. Start with Developer Pain Points

# Prioritization matrix
pain_points:
  high_impact:
    - "Environment provisioning (2+ days)"
    - "Database access"
    - "Certificate management"
  
  medium_impact:
    - "Finding documentation"
    - "Service discovery"
    - "Logs access"
  
  low_impact:
    - "Custom domain setup"
    - "Analytics configuration"

2. Provide Golden Paths, Not Gateways

# โœ… Good: Opinionated but flexible
paths:
  standard:
    language: [go, python, typescript]
    deployment: [kubernetes, cloudrun]
    database: [postgresql, redis]
    # Developers choose from options
  
  # โŒ Bad: Too many choices
  # Let developers choose anything = chaos

3. Measure Platform Success

metrics:
  developer:
    - time_to_first_deployment
    - deployment_frequency
    - onboarding_time
    - dx_satisfaction_score
  
  platform:
    - platform_availability
    - provisioning_success_rate
    - mean_time_to_recovery
    - automated_vs_manual_actions
  
  business:
    - feature_delivery_time
    - engineering_velocity
    - production_incidents

4. Build Feedback Loops

// Feedback collection
app.post('/api/feedback', async (req, res) => {
  const feedback = {
    user: req.user.id,
    rating: req.body.rating,
    category: req.body.category,
    comment: req.body.comment,
    timestamp: new Date(),
    context: {
      page: req.body.page,
      action: req.body.action,
    }
  };
  
  await feedbackStore.save(feedback);
  
  // Trigger alerts for critical issues
  if (feedback.rating <= 2) {
    await alertChannel.send({
      message: `Low DX feedback: ${feedback.category}`,
      priority: 'high',
    });
  }
});

Common Pitfalls

1. Building Without Developer Input

Wrong:

# Built what we thought they needed
platform:
  features:
    - complex_workflows
    - many_approval_steps
    - custom_tooling
# Result: Nobody uses it

Correct:

# Built what they actually needed
platform:
  features:
    - simple_deploy_button
    - automatic_docs
    - instant_feedback
# Result: High adoption

2. Over-Engineering

Wrong:

// Custom platform from scratch
class CustomPlatform {
  async doEverything(): Promise<any> {
    // 10,000 lines of custom code
  }
}

Correct:

// Compose existing tools
const platform = new PlatformBuilder()
  .use(Backstage)
  .use(ArgoCD)
  .use(Tekton)
  .use(OPA)
  .build();

3. Not Providing Support

Wrong:

# Self-service only, no help
platform:
  docs_only: true
  support_channel: none

Correct:

# Self-service + support
platform:
  docs: true
  slack_channel: "#platform-support"
  office_hours: "Thursdays 2pm"
  dedicated_sre: 1

Tools & Technologies

Platform Components

Category Tools
Developer Portal Backstage, Port, Cortex
GitOps ArgoCD, Flux
IaC Terraform, Pulumi, Crossplane
Kubernetes Helm, Kustomize, Brigade
Service Mesh Istio, Linkerd
Secrets Vault, Sealed Secrets
Monitoring Prometheus, Grafana, Jaeger

External Resources

Official Documentation

GitHub & Examples

Learning Resources


Conclusion

Platform engineering represents a fundamental shift in how organizations think about developer tooling. By investing in Internal Developer Platforms, companies can dramatically improve developer productivity, reduce time-to-market, and create consistent, secure paths to production.

The key is starting small, measuring impact, and iterating based on actual developer feedback. Don’t try to build everything at onceโ€”focus on the highest-impact pain points and expand from there.


Key Takeaways

  • Platform engineering creates self-service Internal Developer Platforms
  • Golden paths provide opinionated, standardized routes to production
  • Backstage is the leading open-source developer portal
  • Start with pain points: Don’t build what you think they need
  • Measure success: Track DX, deployment frequency, time-to-first-deploy
  • Provide support: Self-service + help channels for adoption

Comments