← Back to blog

2026-07-30 · 9 min read

What an Internal Developer Platform Actually Looks Like

Beyond the buzzword, a working reference for an Internal Developer Platform: Backstage service catalog, golden-path scaffolder, and the paved-road defaults that tie GitOps, observability, and CI/CD together.

#platform-engineering#backstage#kubernetes#gitops#developer-experience
What an Internal Developer Platform Actually Looks Like

"We're building an Internal Developer Platform" has become the thing every infrastructure team says to justify their next quarter's roadmap. But when you ask what's actually in the platform, answers get vague fast. Backstage? A portal? Kubernetes with extra steps?

I built a reference IDP to answer that question concretely. Not a demo, a working scaffold that wires together the pieces a platform team actually ships: a service catalog with ownership, a golden-path scaffolder that produces fully operational services, and paved-road defaults connecting GitOps, observability, CI/CD, and secrets management. Here's what it looks like in practice.

Platform Engineering Is Not a Tool

Platform engineering is a discipline, not a product. The goal: reduce cognitive load for product teams by curating opinionated, self-service infrastructure behind clean abstractions. Backstage is a portal, a UI layer. The platform is everything behind it: the templates, the automation, the contracts between teams and infrastructure.

A useful mental model:

LayerWhat it doesExample
PortalDiscovery, scaffolding, docsBackstage
Golden pathsOpinionated service templatesScaffolder + cookiecutter
Paved roadsShared infra with sane defaultsGitOps repos, CI templates, monitoring
FoundationKubernetes, networking, IAMEKS/GKE + Terraform

My reference repo (platform-engineering-idp) covers the top three layers. The foundation layer is handled by a separate Terraform repo, this IDP assumes a running cluster exists.

The Service Catalog: Who Owns What

The catalog is the single source of truth for services, their owners, and their dependencies. Every repo contains a catalog-info.yaml that Backstage discovers:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-service
  description: Processes payment intents and webhook events
  annotations:
    github.com/project-slug: durrello/payment-service
    backstage.io/techdocs-ref: dir:.
  tags:
    - golang
    - grpc
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: checkout-system
  providesApis:
    - payment-api
  dependsOn:
    - resource:postgres-payments
    - component:notification-service

This isn't just metadata for a pretty UI. It answers real questions at 2 AM: who owns this service? What does it depend on? Where are the docs? The system field groups related components so you can reason about boundaries, not just individual microservices.

I define Systems and APIs as first-class entities too:

apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  name: checkout-system
  description: End-to-end purchase flow
spec:
  owner: team-payments
  domain: commerce

Ownership is enforced, every Component must declare an owner that maps to a Team entity. No orphan services.

The Golden-Path Scaffolder

This is where the platform becomes self-service. A developer fills out a form in Backstage (service name, team, language, needs a database?) and the scaffolder generates a complete, production-ready repository.

What "production-ready" means in this context:

  • Application code: Go or Node.js skeleton with health checks, graceful shutdown, structured logging
  • Dockerfile: multi-stage, distroless base, non-root user
  • CI pipeline: GitHub Actions workflow referencing shared templates from cicd-pipeline-templates
  • Kubernetes manifests: Deployment, Service, HPA, PodDisruptionBudget in a deploy/ directory
  • GitOps registration: an ApplicationSet generator entry so Argo CD picks up the new service automatically
  • Monitoring: ServiceMonitor + Grafana dashboard JSON provisioned via the observability-stack patterns
  • Secrets: Vault AppRole + ExternalSecret manifest pulling from vault-on-kubernetes paths
  • catalog-info.yaml: pre-filled with the team's ownership

The scaffolder template (templates/golden-path-service/template.yaml) uses Backstage's fetch:template and publish:github actions:

steps:
  - id: fetch
    name: Fetch skeleton
    action: fetch:template
    input:
      url: ./skeleton
      values:
        serviceName: ${{ parameters.serviceName }}
        owner: ${{ parameters.owner }}
        language: ${{ parameters.language }}
        needsDatabase: ${{ parameters.needsDatabase }}

  - id: publish
    name: Create GitHub repo
    action: publish:github
    input:
      repoUrl: github.com?owner=durrello&repo=${{ parameters.serviceName }}
      defaultBranch: main
      protectDefaultBranch: true

  - id: register
    name: Register in catalog
    action: catalog:register
    input:
      repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
      catalogInfoPath: /catalog-info.yaml

A developer goes from "I need a new service" to a deployed, monitored, owned service in under 10 minutes. No Jira ticket to the platform team.

The Paved-Road Philosophy

Golden paths are opinionated by design. But "opinionated" doesn't mean "locked in." The philosophy:

Opinionated defaults: Every generated service ships with structured JSON logging, Prometheus metrics on :9090/metrics, readiness/liveness probes, and a standard CI pipeline. You get these without asking.

Escape hatches: Need a custom build step? Override the CI template's matrix. Need a non-standard port? Change the Helm values. The platform provides guardrails, not walls.

Consistency over flexibility: If 40 services use the same logging format, your observability stack works out of the box. One team's "creative" log format is everyone's on-call nightmare.

The defaults are encoded in shared repos that the IDP references:

ConcernRepoWhat it provides
Deploymentgitops-kubernetes-platformArgo CD ApplicationSets, namespace conventions, RBAC
Monitoringobservability-stackPrometheus + Grafana + Loki stack, shared dashboards
CI/CDcicd-pipeline-templatesReusable GitHub Actions workflows (build, scan, deploy)
Secretsvault-on-kubernetesVault config, AppRole policies, ExternalSecrets operator

Each repo is independently versioned. The IDP pins to specific versions so a platform upgrade doesn't silently break 50 services.

How It All Ties Together

Here's the flow when a developer scaffolds a new service:

  1. Backstage scaffolder creates the repo from the golden-path template
  2. The repo includes a GitHub Actions workflow that imports shared steps from cicd-pipeline-templates, build, container scan, push to registry
  3. An Argo CD ApplicationSet in gitops-kubernetes-platform uses a git-generator to discover the new deploy/ directory and syncs it to the cluster
  4. The Kubernetes manifests include a ServiceMonitor: Prometheus (from observability-stack) scrapes it automatically
  5. An ExternalSecret pulls credentials from Vault paths defined in vault-on-kubernetes
  6. The catalog-info.yaml registers the service in Backstage, ownership, APIs, dependencies all visible immediately

No glue scripts. No manual steps. Each piece does one thing and connects through well-defined interfaces (Git repos, Kubernetes CRDs, Backstage entities).

Key Lessons

Start with the catalog, not the scaffolder. You can't build golden paths if you don't know what you already have. Register existing services first: even manually. Ownership alone is worth the effort.

The platform is the sum of its repos, not a monolith. Keeping GitOps config, CI templates, observability, and secrets in separate repos lets teams adopt incrementally. You don't need to buy the whole platform on day one.

Measure adoption, not coverage. A golden path nobody uses is shelf-ware. Track how many new services use the scaffolder vs. how many are created manually. If teams route around your platform, the platform is wrong.

Treat the platform as a product. It has users (developers), it needs feedback loops, and it competes with "just do it myself." If self-service is slower than a Slack message to your team, you've failed.


The full reference, catalog entities, scaffolder templates, skeleton code, and documentation on how each piece connects, is open source:

GitHub: github.com/durrello/platform-engineering-idp

Fork it, strip out what doesn't fit, and adapt the golden paths to your stack. The structure matters more than the specific tools.

Share:LinkedInXWhatsApp

Related articles

Reactions & comments