Loading...
All Articles
Platform · 7 min read

Platform Engineering: Building Your Internal Developer Platform

Learn how to design and build an Internal Developer Platform (IDP) that accelerates developer productivity, standardizes infrastructure, and reduces cognitive load across your engineering organization.

The Rise of Platform Engineering

Platform engineering has emerged as the natural evolution of DevOps. While DevOps broke down silos between development and operations, many organizations discovered that asking every developer to master Kubernetes, Terraform, and CI/CD pipelines created a different problem: cognitive overload.

Platform engineering addresses this by building an Internal Developer Platform (IDP) — a self-service layer that abstracts infrastructure complexity and lets developers focus on writing code. According to Gartner, by 2026, 80% of software engineering organizations will have established platform teams as internal providers of reusable services, components, and tools.

What Makes a Good Internal Developer Platform

A well-designed IDP provides five core capabilities:

  • Infrastructure provisioning — Developers request environments through a portal or CLI, not Jira tickets
  • Application configuration management — Standardized deployment configurations with sensible defaults
  • Environment management — Consistent dev, staging, and production environments
  • Observability integration — Built-in logging, metrics, and tracing from day one
  • Security guardrails — Policy-as-code enforced transparently, not as blockers

The key principle is golden paths — opinionated but flexible workflows that make the right thing the easy thing.

Architecture of a Modern IDP

A typical Internal Developer Platform consists of these layers:

┌─────────────────────────────────────────────────┐
│              Developer Interface                 │
│   (Portal UI / CLI / API / IDE Plugins)         │
├─────────────────────────────────────────────────┤
│            Service Catalog & Docs                │
│   (Backstage / Port / Cortex)                   │
├─────────────────────────────────────────────────┤
│           Orchestration Layer                    │
│   (Crossplane / Terraform / Pulumi)             │
├─────────────────────────────────────────────────┤
│         Infrastructure Resources                 │
│   (Kubernetes / Cloud Services / Databases)      │
└─────────────────────────────────────────────────┘

Each layer serves a distinct purpose: the interface layer provides self-service access, the catalog organizes what you have, the orchestration layer provisions what you need, and the infrastructure layer runs your workloads.

Step 1: Start with a Service Catalog Using Backstage

Spotify's Backstage has become the de facto standard for developer portals. It provides a unified view of all services, documentation, and infrastructure in your organization.

Install Backstage with a single command:

npx @backstage/create-app@latest
cd my-backstage-app
yarn dev

Define your services using catalog-info.yaml files stored alongside your application code:

# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-service
  description: Handles payment processing and billing
  annotations:
    github.com/project-slug: myorg/payment-service
    backstage.io/techdocs-ref: dir:.
    pagerduty.com/service-id: P1234ABC
  tags:
    - python
    - grpc
    - tier-1
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: billing-platform
  dependsOn:
    - component:user-service
    - resource:payments-db
  providesApis:
    - payments-api

This single file gives your organization visibility into service ownership, dependencies, documentation links, and on-call information.

Step 2: Implement Self-Service Infrastructure with Crossplane

Crossplane extends Kubernetes to provision and manage cloud infrastructure through standard Kubernetes manifests. This means developers use the same kubectl workflows they already know.

Define a reusable infrastructure composition for a database:

# composition.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: postgres-standard
  labels:
    provider: aws
    database: postgresql
spec:
  compositeTypeRef:
    apiVersion: database.platform.io/v1alpha1
    kind: PostgreSQLInstance
  resources:
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          forProvider:
            region: us-east-1
            instanceClass: db.t3.medium
            engine: postgres
            engineVersion: "15"
            allocatedStorage: 20
            publiclyAccessible: false
            storageEncrypted: true
            skipFinalSnapshot: false
            masterUsername: admin
          providerConfigRef:
            name: aws-provider
      patches:
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.allocatedStorage"
        - fromFieldPath: "spec.parameters.size"
          toFieldPath: "spec.forProvider.instanceClass"
          transforms:
            - type: map
              map:
                small: db.t3.micro
                medium: db.t3.medium
                large: db.r6g.large

Now developers request a database with a simple claim:

# database-claim.yaml
apiVersion: database.platform.io/v1alpha1
kind: PostgreSQLInstance
metadata:
  name: my-app-db
  namespace: team-payments
spec:
  parameters:
    storageGB: 50
    size: medium
  compositionSelector:
    matchLabels:
      provider: aws
      database: postgresql

No cloud console access needed. No Terraform expertise required. The platform team defines the guardrails, and developers self-serve within them.

Step 3: Create Software Templates for New Services

Backstage Software Templates let developers scaffold new services with your organization's best practices baked in. Here is a template that creates a new microservice with CI/CD, monitoring, and documentation pre-configured:

# template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: microservice-template
  title: Production Microservice
  description: Creates a new microservice with CI/CD, monitoring, and docs
spec:
  owner: platform-team
  type: service
  parameters:
    - title: Service Details
      required:
        - name
        - owner
        - language
      properties:
        name:
          title: Service Name
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
        owner:
          title: Owner Team
          type: string
          ui:field: OwnerPicker
        language:
          title: Language
          type: string
          enum: [go, python, typescript]
  steps:
    - id: fetch-template
      name: Fetch Template
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
    - id: publish
      name: Create Repository
      action: publish:github
      input:
        repoUrl: github.com?owner=myorg&repo=${{ parameters.name }}
        defaultBranch: main
    - id: register
      name: Register in Catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

When a developer clicks "Create" in the Backstage portal, they get a fully functional repository with Dockerfile, Helm charts, GitHub Actions workflows, Prometheus metrics endpoints, and catalog registration — all in under 60 seconds.

Step 4: Enforce Policies with Open Policy Agent

Your platform should make security and compliance automatic. Use Open Policy Agent (OPA) with Gatekeeper to enforce organizational policies at the Kubernetes admission level:

# policy: require resource limits on all containers
package kubernetes.admission

deny[msg] {
  container := input.review.object.spec.containers[_]
  not container.resources.limits.memory
  msg := sprintf("Container '%v' must have memory limits set", [container.name])
}

deny[msg] {
  container := input.review.object.spec.containers[_]
  not container.resources.limits.cpu
  msg := sprintf("Container '%v' must have CPU limits set", [container.name])
}

deny[msg] {
  input.review.object.spec.containers[_].securityContext.runAsRoot == true
  msg := "Containers must not run as root"
}

These policies are invisible to developers when they follow golden paths — violations only surface when someone strays from the standard templates, and the error messages guide them back.

Step 5: Measure Platform Adoption and Developer Satisfaction

Building a platform without measuring its impact is flying blind. Track these metrics:

  • Time to first deploy — How long from "I have an idea" to running in production
  • Deployment frequency per team — Are teams shipping faster after adopting the platform
  • Self-service ratio — Percentage of infrastructure requests fulfilled without a ticket
  • Developer NPS — Regular surveys measuring developer satisfaction with the platform
  • Mean time to onboard — How quickly new engineers become productive

Use Backstage's built-in TechInsights plugin or build custom dashboards with Prometheus and Grafana to track these metrics over time.

Common Pitfalls to Avoid

Building too much too soon. Start with the highest-pain workflows — typically environment provisioning and CI/CD — and expand from there. A minimal platform that developers actually use beats a comprehensive one they ignore.

Treating it as a pure infrastructure project. Platform engineering is fundamentally about developer experience. If your platform team never talks to developers, you are building the wrong thing.

Making the platform mandatory. Golden paths should be attractive, not enforced through mandates. If developers actively avoid your platform, that is feedback you need to hear.

Ignoring documentation. Every template, every API, every workflow needs clear documentation. Backstage's TechDocs feature lets you write docs as Markdown alongside your code.

Recommended Tool Stack

LayerRecommended Tools
PortalBackstage, Port, Cortex
InfrastructureCrossplane, Terraform, Pulumi
OrchestrationArgo Workflows, Tekton
PolicyOPA/Gatekeeper, Kyverno
SecretsHashiCorp Vault, External Secrets Operator
ObservabilityPrometheus, Grafana, OpenTelemetry
GitOpsArgoCD, Flux

Conclusion

Platform engineering is not about building another tool — it is about building a product for your developers. The most successful platform teams operate like internal startups: they talk to their users, iterate on feedback, and measure outcomes relentlessly.

At DevOpsVibe, we help organizations design and implement Internal Developer Platforms tailored to their engineering culture and technology stack. Whether you are starting from scratch or scaling an existing platform, our team brings hands-on experience with Backstage, Crossplane, and the full cloud-native ecosystem. Get in touch to accelerate your platform engineering journey.

filed under
platform-engineeringidpdeveloper-experiencebackstagekuberneteskubernetesself-service
work with us

Want our team to help with your infrastructure?

talk to an engineerFree 30-min discovery callBook
close