Platform Engineering replaces scattered DevOps toolchains with a paved road — here is how to build an Internal Developer Platform using Backstage as your foundation.
Status: DRAFT
Ask any engineer at a fast-growing startup what slows them down most and you will rarely hear "writing code." You will hear "waiting for environments," "figuring out how to deploy," and "no idea where the docs are." These are not engineering problems. They are toolchain and process problems. Platform Engineering exists to solve them.
An Internal Developer Platform (IDP) is the answer — a curated, self-service layer that sits between your engineers and your infrastructure, so developers can deploy, monitor, and operate services without becoming infrastructure experts.
Before the platform exists, a new engineer joining a team like Swiggy's backend goes through something like this: ask someone for access to the Kubernetes cluster, wait two days for the ticket, figure out which Helm chart to use, find the outdated README, ask on Slack, eventually get something running.
With a platform in place, they open a browser, fill a form, and a service is scaffolded, deployed to staging, wired to monitoring, and added to the service catalog — in twelve minutes.
That twelve-minute flow is the promise of platform engineering. Backstage is the most widely-adopted foundation for building it.
Backstage is an open-source developer portal framework built by Spotify and donated to the CNCF. It is not a finished product — it is a plugin-based framework you assemble around your organization's specific tools and workflows.
Out of the box, Backstage gives you:
The catalog and templates are where most teams start, and they deliver the most immediate value.
Backstage is the portal layer. The IDP underneath it is a stack of tools wired together:
Engineer | vBackstage (portal + catalog + templates) | +-- GitHub / GitLab (source of truth) | +-- Argo CD / Flux (GitOps delivery) | +-- Crossplane / Terraform (infra provisioning) | +-- Prometheus + Grafana (observability) | +-- Kubernetes (runtime)When an engineer clicks "Create New Service" in Backstage, a Software Template triggers a Cookiecutter or Nunjucks scaffold, opens a PR in GitHub, Argo CD detects the new manifests, and the service is deployed — all without the platform team being involved.
## Create Backstage appnpx @backstage/create-app@latest --skip-installcd my-backstage-appyarn install ## Start locallyyarn dev ## opens at localhost:3000The initial setup gets you the UI and catalog. The real work is connecting Backstage to your existing tools via the app-config.yaml:
integrations: github: - host: github.com token: ${GITHUB_TOKEN} catalog: locations: - type: url target: https://github.com/your-org/catalog/blob/main/all.yaml rules: - allow: [Component, API, System] kubernetes: serviceLocatorMethod: type: multiTenant clusterLocatorMethods: - type: config clusters: - url: https://k8s.internal.yourplatform.net name: production authProvider: serviceAccountThis wires Backstage to GitHub for catalog discovery and to your production Kubernetes cluster so engineers can see pod status, logs, and Argo CD sync state directly in the portal.
Software Templates are the most powerful Backstage feature. This is a minimal template that scaffolds a Node.js microservice:
apiVersion: scaffolder.backstage.io/v1beta3kind: Templatemetadata: name: nodejs-microservice title: Node.js Microservice description: Scaffold a production-ready Node.js servicespec: owner: platform-team type: service parameters: - title: Service Details properties: name: title: Service Name type: string owner: title: Owning Squad type: string steps: - id: fetch name: Fetch Template action: fetch:template input: url: ./skeleton ## your template files values: name: ${{ parameters.name }} owner: ${{ parameters.owner }} - id: publish name: Create GitHub Repo action: publish:github input: repoUrl: github.com?owner=your-org&repo=${{ parameters.name }} - id: register name: Register in Catalog action: catalog:register input: repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} catalogInfoPath: /catalog-info.yamlWhen a developer fills this form, Backstage creates the repo, scaffolds the code from your skeleton, and registers the new service in the catalog — automatically.
The catalog is a YAML-driven registry. Each service has a catalog-info.yaml in its repo:
apiVersion: backstage.io/v1alpha1kind: Componentmetadata: name: order-service description: Handles order lifecycle for the platform annotations: github.com/project-slug: your-org/order-service backstage.io/kubernetes-id: order-service pagerduty.com/service-id: P12345spec: type: service lifecycle: production owner: order-squad dependsOn: - component:payment-service - component:inventory-serviceThe dependsOn field is what makes the catalog genuinely useful during incidents: you can click on order-service and immediately see which other services it depends on, who owns them, and whether they are healthy.
Most platform teams underestimate the cultural work. The technology is straightforward — getting 200 engineers to actually use the portal, keep their catalog-info.yaml updated, and stop deploying via direct kubectl apply is where the real effort goes.
Two things that work: mandate that new services must be created through the template (so adoption is automatic for new work), and make the catalog the canonical source for on-call routing and runbook links (so engineers have a reason to keep it updated).
Start with three things only: the catalog, one template, and the Kubernetes plugin. Don't try to integrate everything at once. A useful, partially-populated catalog is better than a perfect empty one.
Run Backstage on Kubernetes using the official Helm chart. Use a PostgreSQL database for the catalog backend — the default in-memory SQLite will lose your catalog every time the pod restarts.
helm repo add backstage https://backstage.github.io/chartshelm install backstage backstage/backstage \ --values values.yaml ## your customized valuesUse Crossplane or Terraform Cloud alongside Backstage for infrastructure provisioning — Backstage is the interface, but it should not do the provisioning itself. Backstage triggers workflows; Crossplane or Terraform executes them.
| Option | Best For | Limitation |
|---|---|---|
| Backstage | Large orgs, customization | High setup investment |
| Cortex | Catalog + scorecards | Less template flexibility |
| Port | Fast time-to-value | Vendor lock-in |
| Humanitec | Opinionated IDP | Less control over UX |
Backstage wins when you have a dedicated platform team (even one person) and more than 30-40 engineers. Below that, the setup cost outweighs the benefit — use a simpler catalog like Cortex or just a well-maintained Notion page.
INFORMATION📚 **References & Further Reading** * [Backstage Documentation](https://backstage.io/docs) - Official setup and plugin development guide * [CNCF Platforms White Paper](https://tag-app-delivery.cncf.io/whitepapers/platforms/) - Platform engineering principles * [Backstage Plugin Marketplace](https://backstage.io/plugins) - 200+ community plugins * [Crossplane Documentation](https://docs.crossplane.io/) - Infrastructure provisioning layer
Switch from URL-based catalog locations to the GitHub Discovery provider, which uses GitHub's API to auto-discover catalog-info.yaml files across all repos without manually registering each one. Pair with a PostgreSQL backend and increase the catalog refresh interval to avoid GitHub API rate limits at scale.
Use Backstage's permissions framework with a custom policy plugin. Define ownership via the spec.owner field in catalog-info.yaml and write policy rules that check the calling user's group membership against the component owner before allowing actions like triggering scaffolder templates or viewing sensitive plugin data.
Discussion0