Building Enterprise IDP with Backstage, Score, and Crossplane for Multi-Cloud Self-Service
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:
- parametersStep 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.engineVersionStep 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:
- Static Policy at Shift-Left: Validate `score.yaml` during CI using `score-k8s validate` and Open Policy Agent (OPA) Conftest.
- 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.
