Platform Engineering in 2026: Building Internal Developer Platforms That Actually Get Used



Platform Engineering in 2026: Building Internal Developer Platforms That Actually Get Used

The platform engineering movement promised to free developers from infrastructure toil. In practice, many organizations built beautiful platforms that nobody wanted to use. This post is about closing that gap — what makes an Internal Developer Platform (IDP) succeed or fail, and how to build one people actually adopt.

Platform Engineering Photo by Markus Spiske on Unsplash


Why Platform Engineering Exists

In 2016, “everyone does DevOps” meant “every developer is also an SRE.” Teams owned their deployments end-to-end. This was empowering until it wasn’t — until every team was reinventing CI/CD pipelines, writing their own Terraform modules, managing their own Kubernetes clusters, and spending more time on infrastructure than product.

Platform engineering is the organizational response: a dedicated team that builds shared tooling so product teams can self-serve. The platform team is a force multiplier.

The goal isn’t to take control away from developers. It’s to make the “right thing” the easy thing — what Camille Fournier calls building the guardrails, not the road.


The Golden Path Concept

A golden path is the platform’s recommended, well-supported way to do something. It’s not mandatory, but it’s the path of least resistance.

A golden path for “deploy a new microservice” might include:

  • A repository template (pre-configured CI/CD, Dockerfile, Kubernetes manifests)
  • A Backstage scaffolder template that generates the repo with one click
  • Pre-wired observability (logs → Loki, metrics → Prometheus, traces → Tempo)
  • A default Kubernetes namespace with pre-configured RBAC
  • A service entry in the internal service catalog

Teams can deviate from the golden path. But the golden path should handle 80% of use cases well enough that deviation is rarely needed.


Backstage: The IDP Framework

Backstage (open-sourced by Spotify, now a CNCF incubating project) has become the de-facto platform for building IDPs. It provides:

  • Software Catalog: a searchable inventory of all your services, APIs, libraries, and teams
  • TechDocs: documentation that lives alongside code
  • Scaffolder: self-service templates for creating new components
  • Plugins: extensible architecture for custom tooling

Setting Up Backstage

# app-config.yaml
app:
  title: ACME Developer Platform
  baseUrl: http://localhost:3000

backend:
  baseUrl: http://localhost:7007
  database:
    client: pg
    connection: ${POSTGRES_URL}

catalog:
  locations:
    - type: url
      target: https://github.com/acme/catalog/blob/main/all-components.yaml
    - type: github-discovery
      target: https://github.com/acme
      catalogPath: /catalog-info.yaml

auth:
  providers:
    github:
      development:
        clientId: ${AUTH_GITHUB_CLIENT_ID}
        clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}

Writing catalog-info.yaml

Every service should have a catalog-info.yaml in its root:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-service
  description: Handles payment processing and refunds
  annotations:
    github.com/project-slug: acme/payment-service
    backstage.io/techdocs-ref: dir:.
    grafana/dashboard-selector: "title='Payment Service'"
    pagerduty.com/service-id: "PAYSERV"
  tags:
    - java
    - payments
    - tier-1
spec:
  type: service
  lifecycle: production
  owner: payments-team
  system: checkout
  providesApis:
    - payment-api
  consumesApis:
    - fraud-detection-api
    - notification-api
  dependsOn:
    - resource:payments-postgres
    - resource:payments-redis

The catalog becomes your organization’s source of truth for “what exists, who owns it, and what depends on what.”


Scaffolder Templates: Self-Service Service Creation

This is where platforms win or lose adoption. If creating a new service takes 30 minutes of CLI commands and Terraform, developers will skip the platform. If it takes 3 minutes in a UI with a form, they’ll use it every time.

# templates/microservice/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: microservice-template
  title: Python Microservice
  description: Create a new Python microservice with FastAPI, Docker, and CI/CD
spec:
  owner: platform-team
  type: service

  parameters:
    - title: Service Details
      required: [name, description, owner]
      properties:
        name:
          title: Service Name
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
        description:
          title: Description
          type: string
        owner:
          title: Owner Team
          type: string
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group
        tier:
          title: Service Tier
          type: string
          enum: [tier-1, tier-2, tier-3]
          description: "Tier 1: critical, on-call required. Tier 3: best-effort."

  steps:
    - id: fetch-base
      name: Fetch Base Template
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: $
          description: $
          owner: $

    - id: create-repo
      name: Create GitHub Repository
      action: publish:github
      input:
        repoUrl: github.com?owner=acme&repo=$
        description: $
        defaultBranch: main

    - id: create-argocd-app
      name: Register in ArgoCD
      action: http:backstage:request
      input:
        method: POST
        path: /api/proxy/argocd/api/v1/applications
        body:
          metadata:
            name: $
          spec:
            source:
              repoURL: https://github.com/acme/$
              path: kubernetes/
            destination:
              server: https://kubernetes.default.svc
              namespace: $

    - id: register
      name: Register in Catalog
      action: catalog:register
      input:
        repoContentsUrl: $
        catalogInfoPath: /catalog-info.yaml

  output:
    links:
      - title: Repository
        url: $
      - title: Open in catalog
        icon: catalog
        entityRef: $

When a developer fills out this form, they get: a GitHub repo with pre-configured CI/CD, a registered ArgoCD application, a catalog entry, TechDocs scaffolded, and Slack channel created — all in 2 minutes, zero tickets.


Kubernetes Abstractions: Less YAML, More Intent

Raw Kubernetes YAML is too low-level for most developers. The platform should provide abstractions.

Crossplane for Infrastructure

Crossplane extends Kubernetes to provision cloud infrastructure using the same declarative model. Your developers write CompositeResourceClaims that your platform team backs with cloud resources.

# Developer writes this (simple, intent-based)
apiVersion: platform.acme.io/v1alpha1
kind: PostgreSQLInstance
metadata:
  name: payment-db
  namespace: payment-service
spec:
  parameters:
    storageGB: 20
    version: "16"
    tier: standard
  writeConnectionSecretToRef:
    name: payment-db-credentials
# Platform team defines what this maps to (Composition)
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: postgresql-aws-rds
spec:
  compositeTypeRef:
    apiVersion: platform.acme.io/v1alpha1
    kind: PostgreSQLInstance
  resources:
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          forProvider:
            region: ap-northeast-2
            instanceClass: db.t3.medium
            engine: postgres
            # ... full RDS config
      patches:
        - fromFieldPath: spec.parameters.storageGB
          toFieldPath: spec.forProvider.allocatedStorage
        - fromFieldPath: spec.parameters.version
          toFieldPath: spec.forProvider.engineVersion

Developers get a simple API. The platform controls what actually gets provisioned.

Score: Application-Centric Spec

Score (CNCF sandbox) takes a different angle — developers describe their workload requirements, and Score translates to the target platform (Kubernetes, Docker Compose, etc.).

# score.yaml — developer writes this, platform translates it
apiVersion: score.dev/v1b1
metadata:
  name: payment-service

containers:
  main:
    image: acme/payment-service:${IMAGE_TAG}
    resources:
      requests:
        cpu: "250m"
        memory: "256Mi"
    variables:
      DATABASE_URL: ${resources.db.connection_string}
      REDIS_URL: ${resources.cache.connection_string}

service:
  ports:
    http:
      port: 8080

resources:
  db:
    type: postgres
    params:
      version: "16"
  cache:
    type: redis

Metrics That Matter for Platform Adoption

The classic DORA metrics are a good starting point:

  • Deployment frequency
  • Lead time for changes
  • Change failure rate
  • Mean time to recovery

But platforms need additional metrics:

MetricWhat It Measures
Template adoption rate% of new services using golden path
Time to first deployHow long from repo creation to first production deploy
Self-service ratio% of infra changes that didn’t require a platform team ticket
Platform NPSDeveloper satisfaction with the platform
Cognitive load incidentsHow often developers escalate due to platform complexity

Track these. If template adoption is low, your templates aren’t solving real problems. If self-service ratio is low, your platform is creating toil instead of reducing it.


The Adoption Trap

The most common platform failure: building for the platform team’s preferences, not developer needs.

Signs you’re in this trap:

  • Your platform’s documentation is comprehensive but developers still open tickets.
  • You built a beautiful portal but people SSH directly to prod servers.
  • Your golden path is “golden” only if you already know how everything works.

The fix: treat developers as your customers. Do user research. Watch developers try to use your platform. Every ticket is a usability failure. Every workaround is a feature gap.

The best platform teams embed with product teams for a week each quarter. They see what pain actually looks like, not what they imagined it looks like.


Platform Team Topologies

Per the Team Topologies framework (Skelton & Pais), a platform team is an enabling team — it exists to increase the autonomy of stream-aligned (product) teams.

The failure mode: the platform team becomes a bottleneck team that every feature has to go through. This happens when:

  • Platform changes require tickets to the platform team
  • The platform has no self-service capabilities
  • The platform team is understaffed relative to the product org

Healthy ratio: Roughly 1 platform engineer per 10-15 product engineers, depending on complexity.


What to Build vs Buy

CapabilityBuildBuy/OSS
Service catalogBackstage
CI/CD platformGitHub Actions, ArgoCD
Kubernetes managementRancher, EKS, GKE
Observability stackDashboards, alertsGrafana, Prometheus, Tempo
Secrets managementVault, AWS Secrets Manager
Service meshIstio, Linkerd, Cilium
Custom abstractionsCrossplane compositions
Internal tooling

Build your differentiators — the abstractions and workflows specific to your organization. Buy (or use OSS) for everything else.


A platform is never finished. The best platform teams treat it like a product: continuous discovery, regular releases, and a feedback loop with their users. The goal isn’t to have the most sophisticated platform — it’s to have the one that makes your developers most productive.


What’s your IDP stack in 2026? Drop a comment or open an issue on GitHub.

이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)