Back to articles
Technology Insight

Building Enterprise IDP with Backstage, Score, and Crossplane for Multi-Cloud Self-Service

August 22, 2026

Building Enterprise Internal Developer Platforms with Backstage, Score, and Crossplane

Modern cloud-native engineering organizations face a persistent paradox: while microservice architectures and cloud APIs grant unprecedented flexibility, they impose an unsustainable cognitive load on software developers. Developers are routinely expected to master Kubernetes manifests, IAM policies, Terraform modules, Helm charts, and cloud-specific routing constructs simply to ship a service.

To solve this, Platform Engineering teams are building Internal Developer Platforms (IDP). An enterprise-grade IDP decouples developer intent from underlying infrastructure mechanics. By synthesizing Backstage (Developer Portal and Service Catalog), Score (workload specification abstraction), and Crossplane (Kubernetes-native infrastructure orchestration engine), platform teams can deliver true self-service multi-cloud infrastructure via GitOps.

Architecture Overview: The Triad Stack

The core architecture divides the developer experience from the platform control plane into three distinct layers:

  • Portal Layer (Backstage): Serves as the single pane of glass, software catalog, and template execution engine.
  • Abstraction Layer (Score): Provides a cloud-agnostic, developer-centric workload specification (score.yaml) that defines requirements without binding to specific implementations.
  • Control Plane Layer (Crossplane & ArgoCD): Crossplane extends Kubernetes into a universal control plane using Composite Resource Definitions (XRDs) to provision cloud resources (AWS RDS, GCP CloudSQL, Azure Database) while ArgoCD synchronizes desired states continuously.
+---------------------------------------------------------------------------------+
|                                 DEVELOPER REALM                                 |
|                                                                                 |
|  +-------------------+        +-------------------+       +------------------+  |
|  |  Backstage UI     |  --->  |   score.yaml      | --->  | Git Repository   |  |
|  | (Software Template|        | (Intent-based Spec|       | (Declarative Repo|  |
|  +-------------------+        +-------------------+       +------------------+  |
+-----------------------------------------|---------------------------------------+
                                          | GitOps Sync (ArgoCD / Flux)
+-----------------------------------------v---------------------------------------+
|                              PLATFORM CONTROL PLANE                             |
|                                                                                 |
|  +---------------------------------------------------------------------------+  |
|  |                   Kubernetes Cluster (Control Plane)                      |  |
|  |                                                                           |  |
|  |  +------------------------+             +------------------------------+  |  |
|  |  | score-k8s / score-xp   |  -------->  | Crossplane Composite Resource|  |  |
|  |  | (Spec Transformer)     |             | (e.g., XPostgreSQLInstance)   |  |  |
|  |  +------------------------+             +------------------------------+  |  |
|  +--------------------------------------------------------|------------------+  |
+-----------------------------------------------------------|---------------------+
                                                            | Cloud Provider APIs
                                                            v
                                     +-----------------------------------------------+
                                     |  AWS RDS  |  GCP CloudSQL  |  Azure Postgres  |
                                     +-----------------------------------------------+

Step 1: Declaring Intent with Score

Developers should not write 200 lines of Helm values or HCL code to request a database. With Score, developers describe what their application needs to run in a environment-agnostic spec.

Here is an enterprise-grade score.yaml file defining a containerized workload that requires a PostgreSQL database resource:

apiVersion: score.dev/v1b1
metadata:
  name: payment-service
containers:
  app:
    image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/finance/payment-service:v2.4.1
    variables:
      LOG_LEVEL: "info"
      DB_HOST: "${resources.db.host}"
      DB_PORT: "${resources.db.port}"
      DB_NAME: "${resources.db.name}"
    resources:
      requests:
        cpu: "500m"
        memory: "512Mi"
      limits:
        cpu: "2"
        memory: "2Gi"
resources:
  db:
    type: postgres
    class: production
    params:
      storageGb: 100
      engineVersion: "15.4"

Step 2: Designing Crossplane Composite Resource Definitions (XRD)

The platform team exposes high-level, opinionated infrastructure primitives to the cluster via Crossplane XRDs. Below is a CompositeResourceDefinition that defines an enterprise PostgreSQL instance requirement (XPostgreSQLInstance):

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xpostgresqlinstances.platform.enterprise.io
spec:
  group: platform.enterprise.io
  names:
    kind: XPostgreSQLInstance
    plural: xpostgresqlinstances
  claimNames:
    kind: PostgreSQLInstance
    plural: postgresqlinstances
  versions:
  - name: v1alpha1
    served: true
    referenceable: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              parameters:
                type: object
                properties:
                  storageGb:
                    type: integer
                    default: 20
                  engineVersion:
                    type: string
                    default: "15"
                  cloudProvider:
                    type: string
                    enum: ["aws", "gcp", "azure"]
                required:
                - storageGb
                - cloudProvider
            required:
            - parameters

Step 3: Implementing Multi-Cloud Crossplane Compositions

Next, the platform team writes a Composition mapping the custom resource to real cloud resources (e.g., AWS RDS Instance, DB Subnet Group, and Security Group):

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: aws-rds-postgres-production
  labels:
    provider: aws
    environment: production
spec:
  compositeTypeRef:
    apiVersion: platform.enterprise.io/v1alpha1
    kind: XPostgreSQLInstance
  resources:
  - name: rds-instance
    base:
      apiVersion: rds.aws.upbound.io/v1beta1
      kind: Instance
      spec:
        forProvider:
          region: us-east-1
          dbSubnetGroupName: enterprise-vpc-db-subnet
          vpcSecurityGroupIdRefs:
          - name: db-sec-group
          allocatedStorage: 20
          engine: postgres
          instanceClass: db.m6i.large
          publiclyAccessible: false
          skipFinalSnapshot: false
    patches:
    - type: FromCompositeFieldPath
      fromFieldPath: spec.parameters.storageGb
      toFieldPath: spec.forProvider.allocatedStorage
    - type: FromCompositeFieldPath
      fromFieldPath: spec.parameters.engineVersion
      toFieldPath: spec.forProvider.engineVersion

Step 4: Automating Provisioning via Backstage Software Templates

To expose this system to developers seamlessly, Backstage uses Software Templates. A template collects developer inputs, generates the repository structure containing `score.yaml`, translates it into native Kubernetes/Crossplane claims using `score-k8s` or custom platform hooks, and creates the Git repository.

apiVersion: backstage.io/v1alpha1
kind: Template
metadata:
  name: microservice-idp-template
  title: Enterprise Cloud-Native Service
  description: Scaffolds a new microservice with automated multi-cloud infrastructure using Score and Crossplane.
spec:
  owner: platform-engineering
  type: service

  parameters:
    - title: Service Configuration
      required:
        - serviceName
        - cloudProvider
      properties:
        serviceName:
          title: Service Name
          type: string
          pattern: '^[a-z0-9-]+$'
        cloudProvider:
          title: Target Cloud Provider
          type: string
          enum:
            - aws
            - gcp
            - azure
          default: aws

  steps:
    - id: fetch-skeleton
      name: Render Base Code and Score Spec
      action: fetch:template
      input:
        url: ./skeleton
        values:
          serviceName: ${{ parameters.serviceName }}
          cloudProvider: ${{ parameters.cloudProvider }}

    - id: publish-git
      name: Publish to Git Provider
      action: publish:github
      input:
        allowedHosts: ['github.com']
        repoUrl: 'github.com?repo=${{ parameters.serviceName }}&owner=enterprise-org'

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

Enterprise Governance & Policy Enforcement

Self-service without governance leads to cloud cost blowouts and security vulnerabilities. When running a Crossplane and Score-driven platform, governance must be enforced at two critical layers:

  1. Static Policy at Shift-Left: Validate `score.yaml` during CI using `score-k8s validate` and Open Policy Agent (OPA) Conftest.
  2. Admission Control at Runtime: Enforce strict Kyverno or OPA Gatekeeper policies on Crossplane Composite Resources (XRDs) inside Kubernetes.

Rule of Thumb: Platform teams must never expose raw cloud provider CRDs (e.g., Upbound AWS Provider CRDs) directly to workloads. Always enforce governance using an intermediate Composite Resource Definition (XRD) layer.

Conclusion

Combining Backstage, Score, and Crossplane provides a clear separation of concerns for platform architectures. Developers gain autonomy using clean, declarative specifications, while platform engineers maintain complete governance over cloud infrastructure, security, and cost optimization across AWS, GCP, and Azure.