Showing posts with label backstage. Show all posts
Showing posts with label backstage. Show all posts

Wednesday, April 15, 2026

Platform Engineering in 2026: Internal Developer Platforms, Backstage, and the Golden Path

Hero: Platform engineering topology diagram showing IDP between developers and infrastructure

There's a pattern that repeats across every organization that scales past 50 engineers: infrastructure becomes a full-time job for product developers. Kubernetes YAML sprawls across repositories. Every team builds their own deployment pipeline. New engineers spend their first month asking "where do I find the runbook for X?" instead of writing features.

Platform engineering is the discipline that solves this. It treats the developer experience as a product — building internal tools, golden paths, and self-service infrastructure so that application developers can deploy, monitor, and operate their services without becoming Kubernetes experts. In 2026, platform engineering has moved from Google/Netflix-scale concern to the standard approach for any organization running more than 10 microservices.

The Problem: Infrastructure as a Blocker

The anti-pattern looks like this: a DevOps team manages Kubernetes, Terraform, CI/CD pipelines, and observability. Every new service requires a ticket to that team. The DevOps team becomes a bottleneck — they're constantly firefighting and can't keep up with service requests. Developers wait days for environment provisioning. Senior engineers spend 20% of their time on infrastructure they shouldn't need to touch.

The cognitive load compounds. A developer who wants to ship a feature must understand: Docker build, Kubernetes manifests, Helm charts, ArgoCD sync, Prometheus alerts, PagerDuty routing, VPC networking, IAM policies. Each of these is a separate expertise domain.

graph LR subgraph "Without Platform Engineering" D1[Dev Team A] -->|"ticket: new service"| O[Ops/DevOps Team] D2[Dev Team B] -->|"ticket: env provision"| O D3[Dev Team C] -->|"ticket: alert setup"| O O --> I[Infrastructure] O -.->|bottleneck| O end

Platform engineering inverts this: the platform team builds self-service capabilities, and application teams use them.

graph LR subgraph "With Platform Engineering" D1[Dev Team A] --> P[Internal Developer Platform] D2[Dev Team B] --> P D3[Dev Team C] --> P P --> I[Infrastructure\nKubernetes, Cloud, CI/CD] PT[Platform Team] -->|builds + operates| P end

The platform team builds once; application teams move fast.

How It Works: The Three Layers

A mature IDP has three layers:

1. Infrastructure Layer: The actual compute, networking, and storage. Kubernetes clusters, cloud accounts, databases. The platform team owns this.

2. Platform Services Layer: Standardized abstractions over infrastructure. Deployment pipelines, secrets management, observability stack, service mesh. Application teams don't configure these directly — they use them through the platform.

3. Developer Interface Layer: The self-service portal, CLI, and documentation that application teams interact with. Backstage is the most common implementation of this layer.

graph TD A[Developer Interface\nBackstage, CLI, Docs] --> B[Platform Services\nCI/CD, Secrets, Observability, Service Mesh] B --> C[Infrastructure\nKubernetes, Cloud, Databases] D[Application Developer] -->|self-service| A E[Platform Team] -->|builds + operates| B E -->|manages| C style A fill:#3b82f6,color:#fff style B fill:#8b5cf6,color:#fff style C fill:#6b7280,color:#fff

Implementation: Building with Backstage

Backstage, open-sourced by Spotify and now a CNCF project, is the most widely adopted IDP frontend. It provides a software catalog, scaffolding templates, and plugin framework.

Setting Up Backstage

# Scaffold a new Backstage app
npx @backstage/create-app@latest --skip-install
cd my-backstage-app
yarn install

# Start dev mode
yarn dev
# → http://localhost:3000

The core concept is the Software Catalog — a centralized registry of all services, APIs, libraries, and infrastructure components. Each component is described by a catalog-info.yaml:

# catalog-info.yaml (checked into each service repo)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payments-service
  description: Processes payment transactions via Stripe
  annotations:
    github.com/project-slug: myorg/payments-service
    backstage.io/techdocs-ref: dir:.
    pagerduty.com/service-id: P123ABC
    prometheus.io/rule: sum(rate(http_requests_total{service="payments"}[5m]))
  tags:
    - payments
    - critical
    - python
  links:
    - url: https://grafana.internal/d/payments
      title: Grafana Dashboard
    - url: https://runbooks.internal/payments
      title: Runbook
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: checkout-platform
  dependsOn:
    - component:postgres-payments
    - component:redis-sessions
  providesApis:
    - payments-api

When every service has this file, Backstage aggregates them into a searchable catalog. Engineers can find any service, see its owner, dependencies, runbooks, and live health status — all in one place.

Service Templates: The Golden Path

The golden path is the opinionated, pre-approved way to create new services. Instead of copy-pasting Kubernetes YAML and Dockerfile from existing services (with inevitable drift), teams use Backstage templates to scaffold new services with all standards pre-baked:

# Template definition (stored in Backstage)
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: python-microservice
  title: Python Microservice
  description: Creates a production-ready Python service with FastAPI, Docker, CI/CD, and Kubernetes manifests
spec:
  owner: platform-team
  type: service

  parameters:
    - title: Service Information
      properties:
        name:
          type: string
          title: Service Name
          pattern: "^[a-z][a-z0-9-]{2,30}$"
        description:
          type: string
          title: Service Description
        owner:
          type: string
          title: Owning Team
          ui:field: OwnerPicker

    - title: Infrastructure
      properties:
        namespace:
          type: string
          title: Kubernetes Namespace
          enum: [production, staging, development]
        replicas:
          type: integer
          title: Initial Replica Count
          default: 2
          minimum: 1
          maximum: 10
        memory_limit:
          type: string
          title: Memory Limit
          default: "512Mi"

  steps:
    - id: fetch-template
      name: Fetch Base Template
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
          namespace: ${{ parameters.namespace }}
          replicas: ${{ parameters.replicas }}

    - id: create-github-repo
      name: Create GitHub Repository
      action: github:repo:create
      input:
        repoUrl: github.com?repo=${{ parameters.name }}&owner=myorg
        description: ${{ parameters.description }}

    - id: push-to-github
      name: Push Template to GitHub
      action: github:repo:push
      input:
        repoUrl: github.com?repo=${{ parameters.name }}&owner=myorg

    - id: register-in-catalog
      name: Register in Catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['create-github-repo'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

    - id: create-github-environments
      name: Setup Environments
      action: github:environment:create
      input:
        repoUrl: github.com?repo=${{ parameters.name }}&owner=myorg
        environments: [development, staging, production]

  output:
    links:
      - title: Repository
        url: ${{ steps['create-github-repo'].output.remoteUrl }}
      - title: Open in Catalog
        url: ${{ steps['register-in-catalog'].output.entityRef }}

The template skeleton (in ./skeleton/) contains the actual files — Dockerfile, FastAPI app structure, GitHub Actions workflow, Kubernetes Helm values, Prometheus alert rules — all templated with the values from the form above.

A developer fills out a form in the Backstage UI, clicks "Create," and in 30 seconds has a GitHub repo with:
- Production-ready Dockerfile with multi-stage build
- FastAPI app with health endpoints
- GitHub Actions CI/CD pipeline deploying to Kubernetes
- Helm chart with resource limits and HPA configured
- Prometheus alerts for error rate and latency
- catalog-info.yaml registering the service in Backstage

This is the golden path. Not "here's the documentation," but "here's the working thing, already configured correctly."

TechDocs: Documentation as Code

Backstage's TechDocs plugin renders Markdown documentation from service repositories directly in the catalog. Documentation lives next to the code, versioned in Git, and is discoverable through Backstage search:

# mkdocs.yml in each service repo
site_name: Payments Service
nav:
  - Home: index.md
  - Architecture: architecture.md
  - API Reference: api.md
  - Runbook: runbook.md
  - On-Call Guide: oncall.md

plugins:
  - techdocs-core
<!-- docs/runbook.md -->
# Payments Service Runbook

## High Error Rate Alert

**Symptom:** `PaymentsHighErrorRate` alert firing  
**Threshold:** Error rate > 5% for 5 minutes

### Immediate Steps
1. Check recent deployments: `kubectl rollout history deploy/payments-service -n production`
2. Check error logs: `kubectl logs -l app=payments-service -n production --tail=100`
3. Check Stripe API status: https://status.stripe.com
...

Engineers find runbooks from the Backstage catalog, not by asking "where's the runbook for X?" in Slack.

The Platform Team's Operating Model

A platform team of 3-5 engineers can support 50-150 application developers when built correctly. The key is operating like a product team, not a shared services team:

Product management: The platform has a roadmap, a backlog prioritized by developer impact, and regular user research with application teams. "What slows you down?" is the core question.

SLOs for the platform: The platform itself has service level objectives. Deployment pipeline P99 runtime < 10 minutes. Backstage availability > 99.5%. Provisioning request time < 2 minutes. Developers treating the platform as a product means they can plan around it.

Self-service by default: If a developer must file a ticket for a common task, that's a product gap. Ticket-worthy tasks should become self-service templates within 2 sprints of being identified.

graph TD A[Identify developer friction] --> B{Ticket volume > 5/week?} B -- Yes --> C[Build self-service template or automation] B -- No --> D[Document workaround] C --> E[Measure adoption] E --> F{Adoption > 80%?} F -- Yes --> G[Retire old process] F -- No --> H[Improve UX or documentation] H --> E

Crossplane: Infrastructure as Kubernetes Resources

Backstage handles the developer interface. Crossplane handles the infrastructure provisioning. Together they form a complete self-service layer.

Crossplane extends Kubernetes with custom resource definitions (CRDs) that represent cloud resources. An application team creates a Kubernetes YAML file to request a database — Crossplane provisions the actual RDS instance in AWS.

# Developer submits this YAML to create a production PostgreSQL database
# No AWS console, no Terraform, no ticket to the platform team
apiVersion: database.example.com/v1alpha1
kind: PostgreSQLInstance
metadata:
  name: payments-db
  namespace: payments-prod
spec:
  parameters:
    storageGB: 100
    engineVersion: "16"
    instanceClass: db.r6g.xlarge
    multiAZ: true
    backupRetentionDays: 30
  compositionRef:
    name: postgresql-aws-production
  writeConnectionSecretToRef:
    name: payments-db-credentials  # Automatically written to K8s Secret

The platform team defines Compositions — the Crossplane resources that translate this high-level request into AWS RDS, security groups, parameter groups, and subnet groups. Application teams only see the high-level API. They can't accidentally provision an unencrypted database or skip backups — the platform composition enforces the defaults.

# Platform team's Composition (defined once, used by all teams)
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: postgresql-aws-production
spec:
  compositeTypeRef:
    apiVersion: database.example.com/v1alpha1
    kind: PostgreSQLInstance
  resources:
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          forProvider:
            region: us-east-1
            encrypted: true           # Always enforced
            iamDatabaseAuthenticationEnabled: true
            deletionProtection: true  # Platform enforces this
      patches:
        - fromFieldPath: spec.parameters.storageGB
          toFieldPath: spec.forProvider.allocatedStorage
        - fromFieldPath: spec.parameters.instanceClass
          toFieldPath: spec.forProvider.instanceClass

This pattern — platform defines the opinionated "what's allowed," teams configure within that envelope — is the essence of platform engineering applied to infrastructure.

Platform Engineering Anti-Patterns

Understanding what platform engineering looks like when done wrong saves months of rework:

Anti-pattern 1: Platform as gatekeeping. The platform team creates a "self-service" portal that still requires a human to approve requests. This is just a ticket system with a UI. Self-service means automated provisioning, not form submission.

Anti-pattern 2: Building everything from scratch. Teams sometimes build custom CI/CD engines, secret managers, and service meshes instead of configuring existing solutions. The result: underdocumented custom tooling that breaks when the original author leaves. Use open-source standards; add value with opinionated configuration.

Anti-pattern 3: No feedback loop. Platform teams that don't regularly talk to developers build tools nobody uses. Run "paper cuts" sessions monthly: what slows you down this sprint? Prioritize accordingly.

Anti-pattern 4: Mandating the golden path without exceptions. Every large organization has legacy services that can't immediately adopt the new platform. Forcing migration causes conflict and backlash. Offer a path that makes new services easy, without blocking teams on legacy systems.

Anti-pattern 5: Platform team as a cost center. Platform engineering has clear ROI — measure it. Time saved per developer per week × number of developers × engineer cost = platform value. Deployment frequency and DORA metrics tell the story quantitatively. A platform team that can't show ROI will be cut in the next budget cycle.

flowchart TD A[Platform team approach] A --> B[Self-service automation\n✅ Anti-pattern 1 fix] A --> C[Configure open-source tooling\n✅ Anti-pattern 2 fix] A --> D[Regular developer feedback\n✅ Anti-pattern 3 fix] A --> E[Opt-in golden path\n✅ Anti-pattern 4 fix] A --> F[Measure DORA + ROI\n✅ Anti-pattern 5 fix] style B fill:#22c55e,color:#fff style C fill:#22c55e,color:#fff style D fill:#22c55e,color:#fff style E fill:#22c55e,color:#fff style F fill:#22c55e,color:#fff

Production Considerations

What Not to Build

Platform teams that try to build everything burn out and produce tools nobody uses. The most common mistake is building custom CI/CD from scratch. Use GitHub Actions, GitLab CI, or Tekton — your value-add is the opinionated workflows on top, not the engine itself.

Similarly, don't build custom secret managers, custom monitoring agents, or custom service mesh implementations. Vault, the OpenTelemetry Collector, and Istio/Cilium are mature. Your job is to configure them correctly and wrap them in self-service abstractions.

Measuring Platform Success

Metrics that matter:
- DORA metrics: Deployment frequency, lead time for changes, change failure rate, mean time to recovery. The platform should improve all four.
- Onboarding time: How long until a new engineer ships their first feature? Platform teams track this.
- Self-service ratio: What percentage of infrastructure requests are fulfilled through self-service vs. tickets?
- Platform adoption: Are teams using the golden paths? Deviations are technical debt.

Multi-Tenancy and Guardrails

The platform enforces standards without blocking innovation. Use Open Policy Agent (OPA) admission controllers to enforce security policies — no privileged containers, no latest image tags, required resource limits — at deploy time rather than in code review.

# OPA/Gatekeeper constraint: require resource limits on all containers
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
  name: require-resource-limits
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    required: ["limits.memory", "limits.cpu", "requests.memory", "requests.cpu"]

Teams can still customize — but they can't accidentally ship a container without resource limits.

The Paved Road vs the Off-Road

Platform engineering doesn't mean mandating one way to do everything. The "paved road" metaphor is more accurate than "golden path": a paved road is smooth, fast, and well-maintained. You can drive off it, but you're aware you're doing so — and you take on more responsibility.

For a Python microservice deploying to Kubernetes, the paved road means:
- FastAPI (not Flask, not Django — one framework, well-supported by the platform)
- Dockerfile from the standard base image (pre-baked security scanning, non-root user)
- GitHub Actions pipeline from the template (not custom pipelines in Jenkins)
- Helm chart from the platform's chart library (not custom Kubernetes YAML)
- Prometheus client pre-integrated (not optional — metrics are mandatory)

Teams that need to go off-road (legacy services, specialized requirements) can — but they own the maintenance. The platform team doesn't guarantee support for custom configurations.

This creates a natural incentive: new services take the paved road because it's genuinely faster. The effort to maintain a custom configuration isn't worth it compared to using the templated, already-working setup.

Operationally, paved-road services benefit from platform improvements automatically. When the platform team upgrades the base Docker image for a security vulnerability, all paved-road services get the fix in their next build — without the service team doing anything. Off-road services have to handle it manually.

The ratio matters: if 80% of services are on the paved road, platform improvements have leverage. If only 20% are, the platform team's work has limited impact.

Developer Portals: Search, Discover, Understand

The unsexy part of platform engineering is documentation and discoverability. Developers spend significant time finding: who owns this service? Where's the runbook? What APIs does it expose? How do I get access to it?

Backstage's search indexes the entire software catalog — services, APIs, documentation, and owners. But the value multiplies when every service has quality catalog-info.yaml and TechDocs. This requires a culture shift: documentation is part of the definition of done.

A practical forcing function: the platform team's deployment pipeline validates that catalog-info.yaml exists and contains required fields before a service can deploy to production.

# GitHub Actions check — runs on every PR
name: Platform Compliance Check

on: [pull_request]

jobs:
  catalog-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Validate catalog-info.yaml exists
        run: |
          if [ ! -f "catalog-info.yaml" ]; then
            echo "❌ catalog-info.yaml is required for all services"
            exit 1
          fi

      - name: Validate required fields
        run: |
          python3 -c "
          import yaml, sys
          with open('catalog-info.yaml') as f:
              catalog = yaml.safe_load(f)

          required = ['metadata.name', 'metadata.description', 'spec.owner', 'spec.lifecycle']
          missing = []
          for field in required:
              keys = field.split('.')
              obj = catalog
              for key in keys:
                  if key not in obj:
                      missing.append(field)
                      break
                  obj = obj[key]

          if missing:
              print(f'❌ Missing required fields: {missing}')
              sys.exit(1)
          print('✅ catalog-info.yaml valid')
          "

      - name: Check TechDocs directory exists
        run: |
          if [ ! -d "docs" ] || [ ! -f "docs/index.md" ]; then
            echo "⚠️  docs/index.md is recommended for all services (see platform wiki)"
          fi

This kind of automated compliance — not blocking deployments for missing docs, but flagging it visibly — moves cultural change faster than documentation mandates alone.

Conclusion

Platform engineering has proven its ROI: organizations that invest in it report 40-60% reduction in time-to-production for new services and significant improvements in DORA metrics. The key insights:

  • Build products, not shared services: Treat your IDP as a product with a roadmap, metrics, and user research
  • Golden paths are opinionated: Offer one well-maintained path rather than infinite flexibility that becomes everyone's problem
  • Self-service or bust: Every ticket-based workflow is a candidate for automation
  • Measure what matters: DORA metrics, onboarding time, and self-service ratio tell you if the platform is working
  • Backstage is the catalog, not the whole platform — the real work is the pipelines, templates, and integrations behind it

The alternative — every team maintaining their own infrastructure — doesn't scale. Platform engineering is the way engineering organizations maintain velocity as they grow.

The most important mindset shift for a platform team: you're not a help desk, you're a product team. Your customers are internal developers. Your product metrics are DORA improvements and developer satisfaction scores. Run user research, maintain a public roadmap, and deprecate unused tools the same way a product team deprecates features. Platform engineering done right is invisible — developers don't notice the infrastructure because it just works.

Getting started doesn't require Backstage on day one. Start with a standardized Dockerfile, a shared GitHub Actions workflow library, and a wiki page listing every service and its owner. That's already more than most teams have. Add Backstage when the catalog needs to be searchable, not before. The principles matter more than the tooling: self-service, golden paths, and measuring developer experience as a first-class metric.


Sources

About the Author

Toc Am

Founder of AmtocSoft. Writing practical deep-dives on AI engineering, cloud architecture, and developer tooling. Previously built backend systems at scale. Reviews every post published under this byline.

LinkedIn X / Twitter

Published: 2026-05-14 · Written with AI assistance, reviewed by Toc Am.

Get These In Your Inbox

Weekly deep-dives on AI engineering, no fluff. Join the newsletter →

Subscribe (free)

Or grab the book ($39, ~100 pages) · Buy me a coffee

Buy Me a Coffee · 🔔 YouTube · 💼 LinkedIn · 🐦 X/Twitter

Sunday, April 12, 2026

Internal Developer Platforms in 2026: Build vs Buy, and the Tools That Win

Internal Developer Platform tools landscape 2026 — comparison dashboard overview

Generated with Higgsfield GPT Image — 16:9

Introduction

Somewhere in your organization, there is a developer waiting on a Jira ticket to get a staging environment. Someone else is copying a service skeleton from a four-year-old repository and manually updating all the CI references. A third person just spent two hours debugging why their Kubernetes deployment failed, only to discover a resource quota was never set on the namespace.

This is not a people problem. It is a platform problem.

Internal Developer Platforms (IDPs) exist to solve exactly these friction points. But in 2026, the market is crowded, the tradeoffs are real, and the cost of choosing wrong is measured in years — not weeks. Companies that bet on Backstage three years ago are now maintaining massive plugin catalogs and struggling to hire React engineers for internal tooling. Companies that chose simpler commercial platforms complain about vendor lock-in and limited extensibility.

This post is a practical guide to the IDP decision. We will cover what is actually inside these platforms, dissect the leading tools in detail, and give you a concrete framework for choosing between building and buying. If you are a platform engineer, engineering manager, or architect evaluating IDPs in 2026, this is the post you need to read before you commit.


The IDP Landscape in 2026

The Internal Developer Platform market has consolidated significantly since 2022. Early fragmentation — dozens of point solutions for catalog, scaffolding, environment provisioning, and scorecards — has given way to a handful of comprehensive platforms that each take a distinct architectural stance.

Backstage remains the dominant open-source option. Created by Spotify, adopted by the CNCF, and now with over 28,000 GitHub stars and 2,700+ production deployments, it has become the de facto industry vocabulary for IDPs. But Backstage's success has also revealed its limitations as organizations scale.

Port has emerged as the fastest-growing commercial alternative. Its API-first catalog model, no-code blueprint system, and self-service action engine have attracted companies that want value within weeks rather than months. Port closed a Series B in 2024 and has been doubling year-over-year since 2022.

Humanitec targets enterprises that want a Platform Orchestrator — an API layer that sits between developer intent and infrastructure execution. Mercedes-Benz, TIER Mobility, and other large enterprises have adopted it as the backbone of their platform.

Cortex and OpsLevel occupy a specific niche: catalog and engineering excellence scoring. They are less about self-service provisioning and more about visibility, accountability, and quality enforcement.

Configure8 is an emerging player focused on making catalog data actionable via deep integrations with existing engineering tools.

The market is moving toward composability: rather than one monolithic IDP, organizations are assembling best-of-breed components around a central catalog. But the complexity of composing and maintaining that stack has renewed interest in consolidated commercial platforms.


Backstage Deep Dive

Backstage is the right choice for many organizations — but it demands respect. Its architecture is sophisticated, its plugin ecosystem is both its greatest strength and its greatest liability, and the maintenance burden can surprise teams that underestimate it.

How Backstage Works

Backstage has three core pillars:

1. Software Catalog — The catalog ingests catalog-info.yaml files from your repositories, Kubernetes clusters, CI systems, and other integrations. It models your technical ecosystem as entities: Components (services, libraries, websites), APIs, Resources (databases, S3 buckets), Systems (logical groupings), and Domains (business units).

2. TechDocs — Backstage includes a documentation system that reads docs/ directories from your repositories and renders them as searchable HTML inside the portal. Developers write Markdown; the platform handles discovery and search.

3. Software Templates (Scaffolder) — The scaffolder lets platform teams define parameterized templates that create GitHub repositories, open PRs, provision infrastructure, and register catalog entries — all from a self-service form in the Backstage UI.

Here is a complete catalog-info.yaml showing the full range of metadata Backstage can track for a production service:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: recommendation-engine
  title: Recommendation Engine
  description: "ML-powered product recommendation service (collaborative filtering + content-based)"
  annotations:
    # Source control
    github.com/project-slug: "acmecorp/recommendation-engine"

    # CI/CD
    github.com/workflow-name: "ci-cd.yml"

    # Observability
    datadoghq.com/service-name: "recommendation-engine"
    datadoghq.com/dashboard-url: "https://app.datadoghq.com/dashboard/rec-engine"

    # Incident management
    pagerduty.com/service-id: "PML123"
    pagerduty.com/integration-key: "abc123xyz"

    # Cost attribution
    aws.amazon.com/cost-center: "ML-PLATFORM"

    # Kubernetes
    backstage.io/kubernetes-id: "recommendation-engine"
    backstage.io/kubernetes-namespace: "ml-services"

    # SLO
    slo: "p99_latency<200ms, availability>99.9%"

  tags:
    - ml
    - python
    - critical-path
    - tier-1

  links:
    - url: https://runbook.internal/recommendation-engine
      title: Runbook
      icon: docs
    - url: https://grafana.internal/d/rec-engine
      title: Grafana Dashboard
      icon: dashboard

spec:
  type: service
  lifecycle: production
  owner: group:ml-platform
  system: personalization
  dependsOn:
    - component:user-profile-service
    - component:product-catalog-service
    - resource:redis-recommendations-cache
    - resource:rds-ml-features-db
  providesApis:
    - recommendations-api
  consumesApis:
    - user-events-api
    - product-catalog-api

Backstage Software Templates

The scaffolder is where Backstage delivers its highest developer ROI. Here is a simplified but complete scaffolder template that creates a new Go microservice:

# template.yaml — Backstage Scaffolder Template
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: go-microservice-template
  title: Go Microservice
  description: Creates a production-ready Go microservice with CI, observability, and catalog registration
  tags:
    - go
    - microservice
    - recommended
spec:
  owner: group:platform-team
  type: service

  parameters:
    - title: Service Information
      required: [name, description, owner]
      properties:
        name:
          title: Service Name
          type: string
          description: Lowercase, hyphenated (e.g. 'inventory-sync')
          pattern: '^[a-z][a-z0-9-]{2,48}[a-z0-9]$'
        description:
          title: Description
          type: string
        owner:
          title: Owner
          type: string
          ui:field: OwnerPicker
          ui:options:
            allowedKinds: [Group]
        tier:
          title: Service Tier
          type: string
          default: tier-2
          enum: [tier-1, tier-2, tier-3]
          enumNames:
            - 'Tier 1 (Critical Path, 99.9% SLA)'
            - 'Tier 2 (Standard, 99.5% SLA)'
            - 'Tier 3 (Internal, best effort)'

    - title: Infrastructure Options
      properties:
        needsDatabase:
          title: Needs PostgreSQL database?
          type: boolean
          default: false
        needsCache:
          title: Needs Redis cache?
          type: boolean
          default: false
        initialReplicas:
          title: Initial replica count
          type: integer
          default: 2
          minimum: 1
          maximum: 10

  steps:
    - id: fetch-skeleton
      name: Fetch Go service skeleton
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          description: ${{ parameters.description }}
          owner: ${{ parameters.owner }}
          tier: ${{ parameters.tier }}
          needs_database: ${{ parameters.needsDatabase }}
          needs_cache: ${{ parameters.needsCache }}

    - id: publish-github
      name: Create GitHub Repository
      action: publish:github
      input:
        allowedHosts: ['github.com']
        repoUrl: 'github.com?repo=${{ parameters.name }}&owner=acmecorp'
        defaultBranch: main
        repoVisibility: private
        deleteBranchOnMerge: true
        requireCodeOwnerReviews: true

    - id: register-catalog
      name: Register in Service Catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['publish-github'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

    - id: create-datadog-dashboard
      name: Create Datadog Dashboard
      action: http:backstage:request
      input:
        method: POST
        path: /api/proxy/datadog/api/v1/dashboard
        body:
          title: '${{ parameters.name }} Service Dashboard'
          description: 'Auto-generated by platform scaffolder'
          widgets: []  # Platform team maintains a default widget set

  output:
    links:
      - title: Repository
        url: ${{ steps['publish-github'].output.remoteUrl }}
      - title: Open in Catalog
        icon: catalog
        entityRef: ${{ steps['register-catalog'].output.entityRef }}

Backstage Plugins

The plugin ecosystem is where Backstage either soars or stumbles, depending on your team's investment. Core plugins are maintained by Backstage contributors and are generally high quality. Community plugins vary enormously — some are production-grade and actively maintained, others were written by one engineer for a weekend project and have not been updated in two years.

Well-maintained plugins in 2026 include: GitHub Actions (CI visibility), Kubernetes (pod status in the catalog), Datadog (metrics in the service page), ArgoCD (deployment status), Vault (secrets access), and AWS Cost Explorer (cost attribution).

The maintenance reality: a typical large Backstage installation in 2026 has 30-80 active plugins. Keeping them current with Backstage core releases requires a dedicated engineer or small team. This is not a criticism — it is a known tradeoff that teams must budget for.

Backstage hosting options have improved. You can self-host Backstage on Kubernetes (the most common approach), use managed Backstage from providers like Roadie.io or Spotify's own Backstage as a Service offering, or deploy it to Render, Railway, or a similar PaaS. Self-hosted on Kubernetes gives maximum control but requires maintaining the deployment, database (PostgreSQL), and ingress. Managed options reduce operational overhead at the cost of some customization and a monthly fee.


Port Deep Dive

Port takes the opposite philosophical stance from Backstage. Where Backstage gives you maximum flexibility at the cost of maximum complexity, Port optimizes for rapid adoption and low operational overhead.

Blueprints: Port's Core Data Model

In Port, everything is a blueprint — a schema definition for a resource type in your organization. You define the properties, relationships, and lifecycle states for each type of resource, then connect Port to your existing tools to populate it with live data.

{
  "identifier": "microservice",
  "title": "Microservice",
  "icon": "Service",
  "schema": {
    "properties": {
      "language": {
        "type": "string",
        "title": "Language",
        "enum": ["Go", "Python", "TypeScript", "Java", "Rust"]
      },
      "tier": {
        "type": "string",
        "title": "Service Tier",
        "enum": ["tier-1", "tier-2", "tier-3"]
      },
      "has_slo": {
        "type": "boolean",
        "title": "Has SLO Defined"
      },
      "p99_latency_ms": {
        "type": "number",
        "title": "P99 Latency (ms)"
      },
      "monthly_cost_usd": {
        "type": "number",
        "title": "Monthly Cloud Cost (USD)"
      },
      "on_call_team": {
        "type": "string",
        "title": "On-Call Team"
      }
    },
    "required": ["language", "tier", "on_call_team"]
  },
  "mirrorProperties": {},
  "calculationProperties": {
    "health_score": {
      "title": "Health Score",
      "calculation": ".properties.has_slo and .properties.p99_latency_ms < 200 | if . then 100 else 50 end",
      "type": "number"
    }
  },
  "relations": {
    "deployment": {
      "title": "Current Deployment",
      "target": "deployment",
      "required": false,
      "many": false
    },
    "owner": {
      "title": "Owner Team",
      "target": "team",
      "required": true,
      "many": false
    }
  }
}

Port's self-service actions let developers trigger operations — create a new service, request a database, scale a deployment — via Port's UI, and Port executes them through your existing CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins) or via webhooks. No custom UI development required.

A self-service action in Port that triggers a GitHub Actions workflow looks like this:

{
  "identifier": "scaffold-microservice",
  "title": "Scaffold New Microservice",
  "icon": "Service",
  "userInputs": {
    "properties": {
      "service_name": {
        "type": "string",
        "title": "Service Name",
        "description": "Lowercase, hyphenated"
      },
      "language": {
        "type": "string",
        "title": "Language",
        "enum": ["go", "python", "typescript"]
      },
      "owner_team": {
        "type": "string",
        "title": "Owner Team",
        "blueprint": "team",
        "format": "entity"
      }
    },
    "required": ["service_name", "language", "owner_team"]
  },
  "invocationMethod": {
    "type": "GITHUB",
    "org": "acmecorp",
    "repo": "platform-actions",
    "workflow": "scaffold-service.yml",
    "omitPayload": false,
    "reportWorkflowStatus": true
  },
  "trigger": "CREATE",
  "description": "Scaffold a new microservice with CI/CD, catalog registration, and observability"
}

Port's scorecards enable engineering excellence tracking: teams can define standards (every service must have an SLO, must have a runbook, must have fewer than 10 critical CVEs) and track compliance across the entire catalog in real time. The scorecard dashboard shows each team's compliance percentage, which services are failing which standards, and trends over time. This gamification element drives organic adoption — teams want to improve their scores, which means following the platform standards.


Humanitec: The Platform Orchestrator Model

Humanitec's approach is the most architecturally distinct of the three major players. While Backstage and Port are primarily portals — developer-facing UIs on top of your existing infrastructure — Humanitec positions itself as a Platform Orchestrator: an API layer that translates high-level developer intent into concrete infrastructure configurations.

The Score Model

Humanitec's core concept is the Score file — a simple, infrastructure-agnostic workload specification that developers write once and the orchestrator compiles to any target infrastructure (Kubernetes manifests, Helm releases, Terraform configs, ECS task definitions).

# score.yaml — developer writes this, orchestrator compiles it
apiVersion: score.dev/v1b1
metadata:
  name: order-processor

containers:
  order-processor:
    image: acmecorp/order-processor:${IMAGE_TAG}
    variables:
      DATABASE_URL: ${resources.postgres.connection_string}
      REDIS_URL: ${resources.cache.connection_string}
      LOG_LEVEL: info
    resources:
      requests:
        cpu: "100m"
        memory: "128Mi"
      limits:
        cpu: "500m"
        memory: "512Mi"
    livenessProbe:
      httpGet:
        path: /health
        port: 8080

service:
  ports:
    http:
      port: 80
      targetPort: 8080

resources:
  postgres:
    type: postgres
    class: standard
  cache:
    type: redis
    class: standard

The platform team defines resource definitions that specify how each resource type (postgres, redis, s3) should be provisioned in each environment class (dev, staging, production). Developers never write Kubernetes YAML or Terraform — they declare what their service needs, and the orchestrator handles the rest.

Humanitec's dynamic environment configuration solves a specific pain point at scale: managing hundreds of staging and preview environments where each one needs its own resource instances with correct connection strings injected automatically.


Build vs Buy Decision Framework

With the tools understood in detail, the decision framework becomes clearer. The right choice depends on five variables: team size, operational maturity, budget, customization needs, and how quickly you need to deliver value.

IDP tools comparison — Backstage vs Port vs Humanitec vs Cortex side by side

Generated with Higgsfield GPT Image — 16:9

Full Comparison Table

Dimension Backstage Port Humanitec Cortex
Cost Free (self-hosted) $15-25/user/mo Contact sales $20-35/user/mo
Hosting Self-hosted SaaS SaaS + self-hosted SaaS
Setup Time 2-8 weeks 1-3 days 1-2 weeks 1-3 days
Customization Unlimited (React) Medium (no-code config) High (API-first) Low-medium
Scaffolding Yes (powerful) Yes (via actions) Via Score/CI Limited
Catalog Excellent Excellent Good Excellent
Scorecards Via plugins Built-in Limited Built-in (primary feature)
Env Provisioning Via plugins Via actions Native (core feature) No
Vendor Lock-in None Medium Medium Medium
Maintenance Burden High Low Low Low
Best For Large orgs, custom needs Fast adoption, SMB/enterprise Dynamic environments, complex infra Engineering quality tracking

The Decision Tree

flowchart TD A[Start: Evaluating IDPs] --> B{Primary Goal?} B -->|Engineering excellence\n& catalog visibility| CORTEX[Cortex or OpsLevel\nFocus: scorecards + catalog] B -->|Dynamic environment\nprovisioning at scale| HUMANITEC[Humanitec\nFocus: orchestration] B -->|Full self-service portal\nfor developers| C{Budget & team?} C -->|Limited budget,\nsmall platform team| D{Need quick wins?} C -->|Budget available,\nlarge platform team| E{Customization priority?} D -->|Yes, need value in days| PORT[Port\nSaaS, fast setup] D -->|No, can invest time| F{React expertise\nin-house?} F -->|Yes| BACKSTAGE[Backstage\nOpen source, full control] F -->|No| PORT E -->|Maximum extensibility| BACKSTAGE E -->|Fast adoption over control| PORT style BACKSTAGE fill:#d4edda,stroke:#28a745 style PORT fill:#cce5ff,stroke:#004085 style HUMANITEC fill:#f8d7da,stroke:#721c24 style CORTEX fill:#fff3cd,stroke:#856404

Self-Service in Practice

Theory is useful, but the real test of any IDP is the end-to-end experience for a developer who needs something. Let us walk through a complete self-service flow: a developer needs to create a new Python microservice and get it to staging.

Without an IDP (traditional path):
1. Copy an existing service repo, spend 30 minutes removing its specific logic
2. Update CI workflow references, fix the Docker build, update environment variables
3. File a Jira ticket to DevOps for a Kubernetes namespace (wait 1-3 days)
4. Set up Datadog monitoring manually
5. Add a PagerDuty service and link it to your team
6. Add the service to the internal wiki (or don't — this is how undiscoverable services happen)

Total time: 1-3 days, lots of context switching, high error risk.

With an IDP (golden path):

sequenceDiagram participant Dev as Developer participant Portal as IDP Portal participant GH as GitHub participant CI as CI/CD participant K8s as Kubernetes participant Catalog as Service Catalog participant Obs as Observability Stack Dev->>Portal: Select "New Python Microservice" template Dev->>Portal: Fill form: name=inventory-sync, owner=@supply-chain-team, env=staging Portal->>GH: Create repo from template (cookiecutter / scaffolder) GH-->>Portal: Repo created: acmecorp/inventory-sync ✓ Portal->>GH: Trigger initial CI workflow GH->>CI: Run: lint → test → build docker → push to ECR CI-->>GH: All checks passed ✓ Portal->>K8s: Create namespace: inventory-sync-staging K8s-->>Portal: Namespace + RBAC + NetworkPolicy created ✓ Portal->>K8s: Apply workload manifests (from Score or Helm chart) K8s-->>Portal: Deployment running (1/1 pods ready) ✓ Portal->>Obs: Register APM service + create default dashboard Obs-->>Portal: Dashboard URL ready ✓ Portal->>Catalog: Register catalog-info.yaml entity Catalog-->>Portal: Service discoverable ✓ Portal-->>Dev: ✅ Ready! Repo · Dashboard · Staging URL · Runbook template Note over Dev,Obs: Total elapsed time: ~4 minutes

This is the concrete value proposition of an IDP. Four minutes versus two days. Zero Jira tickets. Zero waiting. The developer stays in flow.

Platform Maturity Model

Organizations do not build a complete IDP overnight. A useful framework for understanding where you are and where you are going is the Platform Engineering Maturity Model. It has four levels:

graph LR subgraph L1["Level 1: Reactive"] direction TB R1[Manual processes\nTicket-driven ops\nNo catalog] R2[Each team\ninvents own tooling] end subgraph L2["Level 2: Consistent"] direction TB C1[Shared CI/CD templates\nBasic service catalog\nGolden path v1] C2[Platform team formed\nStandardized on 1 IDP tool] end subgraph L3["Level 3: Scalable"] direction TB S1[Full self-service\nEnv provisioning\nScorecards + compliance] S2[Developer portal\nadoption > 80%\nDORA metrics tracked] end subgraph L4["Level 4: Optimized"] direction TB O1[AI-assisted platform\nPredictive cost management\nSelf-healing environments] O2[Platform as profit center\nExternal developer satisfaction\nIndustry benchmark] end L1 -->|"3-6 months\nfocus: catalog + CI templates"| L2 L2 -->|"6-12 months\nfocus: self-service + golden path"| L3 L3 -->|"12-24 months\nfocus: AI + advanced automation"| L4 style L1 fill:#fce4ec,stroke:#c62828 style L2 fill:#fff3e0,stroke:#f57c00 style L3 fill:#e8f5e9,stroke:#2e7d32 style L4 fill:#e3f2fd,stroke:#1565c0

Most organizations evaluating IDPs for the first time are at Level 1 or early Level 2. The maturity model is useful for scoping expectations: if you are starting at Level 1, do not try to build a Level 3 platform in three months. Start with the catalog and CI templates. Earn developer trust. Then expand.

Self-service developer flow — from request to running service in minutes

Generated with Higgsfield GPT Image — 16:9


Common Pitfalls

Platform engineering is not immune to failure modes. Understanding them in advance saves expensive lessons.

Building the platform nobody uses. This is the most common platform engineering failure. A team spends six months building an IDP and then discovers that developers are still using their old ad-hoc processes. The root cause: the platform was built without involving the developers who would use it. Platform teams that run regular developer experience interviews, track adoption metrics, and treat their users as customers avoid this trap.

Forcing the golden path. A golden path should be so good that developers choose it willingly, not because they have no alternative. When platform teams mandate their path without earning developer trust, developers route around it — using personal scripts, shadow CI pipelines, and workarounds that create more fragmentation than existed before.

Underestimating Backstage maintenance. Backstage core releases new versions frequently. Plugin compatibility is not guaranteed across major versions. Organizations that allocate zero ongoing engineering time for Backstage maintenance end up with installations frozen at a year-old version, missing security patches and features. Budget at least 0.5 FTE for a small installation, 1-2 FTE for a large one.

Not treating the platform as a product. The platform team that disappears after initial launch and considers the IDP "done" is setting itself up for irrelevance. Platforms require continuous iteration, deprecation of old features, onboarding support, and proactive engagement with developer feedback. The platform's product manager role — whether or not that title exists — is a full-time job.

Catalog rot. A service catalog is only valuable if it stays current. Without enforcement and automation, catalog-info.yaml files become stale within weeks of a service's architecture changing. Production IDPs address this through automated validation pipelines that fail CI if catalog metadata is missing required fields, scheduled jobs that detect orphaned catalog entries for deleted repositories, and regular catalog health reviews as part of platform team ceremonies.

Ignoring non-happy-path workflows. Golden path templates are designed for the common case: a new stateless microservice. But real engineering organizations have heterogeneous workloads: batch jobs, ML training pipelines, data streaming services, mobile backends. A platform that only supports the happy path forces teams building non-standard services to go completely off-road. Successful platform teams maintain a catalog of supported patterns and provide clear documentation for teams that need to deviate.


Measuring IDP Success

How do you know if your IDP is working? The metrics fall into three categories.

Developer adoption metrics are the most direct measure of whether the platform is delivering value. Track: percentage of new services created via the golden path (target: 80%+), percentage of teams using the catalog actively (viewed or updated a catalog entry in the last 30 days), and self-service action completion rate (did developers successfully complete self-service requests without abandoning or filing tickets?).

Developer experience scores are the qualitative complement to adoption metrics. A quarterly developer satisfaction survey with five to ten questions about infrastructure cognitive load, time spent on platform tasks, and confidence in tooling provides a trend line that reveals whether the platform is actually reducing friction.

Engineering performance metrics (DORA) measure downstream impact. As IDP adoption increases, deployment frequency should rise, lead time for changes should fall, and MTTR should improve. Platform teams that cannot demonstrate DORA improvement after six months should re-examine whether they are solving the right problems.

The platform team's internal dashboard should surface all of these metrics in one place. Ironically, the best platform teams build their own catalog entry and scorecard — treating the platform itself as a first-class service subject to the same standards they ask of everyone else.


Conclusion

The IDP market has matured enough in 2026 that the question is no longer "should we have an internal developer platform?" — it is "which approach fits our organization, and how do we avoid the two-year rebuild trap?"

Backstage is the right answer for organizations with a strong engineering culture, budget for ongoing React engineering, and a need for maximum customization. Port is the right answer for organizations that need rapid value and are comfortable with a SaaS model. Humanitec is right for enterprises with complex, dynamic environment provisioning needs. Cortex is right for organizations that need engineering excellence tracking first and everything else second.

The common thread in successful IDP implementations is treating the platform as a product. Developer experience is the product. Developer time is the metric. Adoption is the success criteria.

Whatever tool you choose, the platform team's mission is the same: make it so easy to do the right thing that developers never have to think about the infrastructure underneath. When the platform disappears into the background, it has succeeded.


Part of the Platform Engineering Deep-Dive series. Next: AI-Powered Cybersecurity.

About the Author

Toc Am

Founder of AmtocSoft. Writing practical deep-dives on AI engineering, cloud architecture, and developer tooling. Previously built backend systems at scale. Reviews every post published under this byline.

LinkedIn X / Twitter

Published: 2026-04-12 · Written with AI assistance, reviewed by Toc Am.

Get These In Your Inbox

Weekly deep-dives on AI engineering, no fluff. Join the newsletter →

Subscribe (free)

Or grab the book ($39, ~100 pages) · Buy me a coffee

Buy Me a Coffee · 🔔 YouTube · 💼 LinkedIn · 🐦 X/Twitter

Bigger Is Not the Same as Better. The Job That Moved Is the Phone, Not the Lab.

Bigger is a plan. The phone is the receipt. The brief for this cycle is a question: does bigger always mean better in AI? The 2026 answer i...