Building Enterprise Developer Platforms with Backstage, Score, and Crossplane
Building Enterprise Internal Developer Platforms with Backstage, Score, and Crossplane
As enterprises scale their multi-cloud footprint across AWS, GCP, and Azure, developer cognitive load has reached a critical breaking point. Software engineers are frequently forced to navigate complex Cloud Provider Consoles, intricate Infrastructure-as-Code (IaC) templates (Terraform/Pulumi), and raw Kubernetes YAML manifests just to ship a single microservice. This fragmentation creates severe operational bottlenecks, security governance gaps, and shadow IT.
To solve this, leading DevOps organizations are building Internal Developer Platforms (IDPs). This article explores a battle-tested architecture combining three open-source pillars: Backstage (Developer Portal UX & Service Catalog), Score (Environment-agnostic Workload Specification Standard), and Crossplane (Kubernetes-native Universal Control Plane).
The Modern IDP Triad Architecture
An enterprise-grade IDP must enforce a clean separation of concerns between Platform Engineers (who construct infrastructure abstractions and governance guardrails) and Application Developers (who consume resources and deploy workloads).
- Backstage (Single Pane of Glass): Provides the unified developer portal, cataloging microservices, documentation, software templates, and real-time observability indicators.
- Score (Workload Abstraction Layer): Provides an open-source, developer-centric specification (
score.yaml) that abstracts application requirements (containers, environment variables, dependent resources like databases and queues) away from target environments. - Crossplane (Universal Control Plane): Extends Kubernetes CRDs to orchestrate multi-cloud infrastructure directly via the Kubernetes API, replacing traditional asynchronous IaC pipelines with continuous reconciliation loops.
The architectural control loop operates as follows:
+-----------------------------------------------------------------------------------+
| DEVELOPER REALM |
| +------------------------+ +------------------------------+ |
| | Backstage Portal | --- (Scaffold) --> | Git Repo (code + score.yaml) | |
| | (Catalog & Scaffolder)| +------------------------------+ |
| +------------------------+ | |
+----------------------------------------------------------------|------------------+
| Git Push / CI
v
+-----------------------------------------------------------------------------------+
| PLATFORM REALM |
| +------------------------+ +------------------------------+ |
| | score-k8s / Translator| --- (Generates) --> | Crossplane Claims & K8s Spec | |
| +------------------------+ +------------------------------+ |
| | |
| ArgoCD / GitOps |
| v |
| +-----------------------------------------------------------------------------+ |
| | Kubernetes Platform Cluster | |
| | +--------------------+ +----------------------------+ | |
| | | Application Pods | | Crossplane Control Plane | | |
| | +--------------------+ +----------------------------+ | |
| +-----------------------------------------------------------|-----------------+ |
+--------------------------------------------------------------|--------------------+
| API Reconciliation
v
+----------------------------------------------------+
| Multi-Cloud Infrastructure (AWS RDS, GCP SQL, etc) |
+----------------------------------------------------+1. Crossplane: Abstracting Cloud Infrastructure with Compositions
Platform engineers define reusable, compliance-checked infrastructure building blocks using Crossplane Composite Resource Definitions (XRDs) and Compositions. Below is an XRD defining a unified SQL database resource that abstract away whether the implementation is AWS RDS or GCP CloudSQL.
# definition.yaml: CompositeResourceDefinition (XRD)
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresinstances.platform.enterprise.io
spec:
group: platform.enterprise.io
names:
kind: XPostgresInstance
plural: xpostgresinstances
claimNames:
kind: PostgresInstance
plural: postgresinstances
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
storageGB:
type: integer
default: 20
engineVersion:
type: string
default: "15"
required:
- storageGBNext, the platform team maps this XRD to concrete provider resources using a Crossplane Composition tailored for AWS RDS:
# composition-aws.yaml: Composition mapping to AWS RDS
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xpostgresinstance.aws.rds
labels:
provider: aws
db: postgresql
spec:
compositeTypeRef:
apiVersion: platform.enterprise.io/v1alpha1
kind: XPostgresInstance
resources:
- name: rds-instance
base:
apiVersion: database.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
region: us-east-1
dbSubnetGroupName: enterprise-vpc-subnets
engine: postgres
instanceClass: db.t4g.medium
allocatedStorage: 20
skipFinalSnapshot: true
publiclyAccessible: false
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.storageGB
toFieldPath: spec.forProvider.allocatedStorage
- type: FromCompositeFieldPath
fromFieldPath: spec.engineVersion
toFieldPath: spec.forProvider.engine2. Score: Standardizing the Workload Specification
Instead of requiring developers to write raw Kubernetes Deployments, Services, and Crossplane Resource Claims, developers specify workload intentions using a declarative score.yaml.
# score.yaml: Developer Workload Specification
apiVersion: score.dev/v1b1
metadata:
name: payment-service
containers:
web:
image: 123456789.dkr.ecr.us-east-1.amazonaws.com/payment-service:v2.4.1
variables:
PORT: "8080"
DB_HOST: "${resources.db.host}"
DB_NAME: "${resources.db.name}"
DB_USER: "${resources.db.username}"
DB_PASSWORD: "${resources.db.password}"
resources:
db:
type: postgres
properties:
host: metadata.name
name: metadata.database
username: status.username
password: status.passwordDuring the CI/CD pipeline, the CLI tool score-k8s or custom Score translators convert this file into native Kubernetes manifests alongside a Crossplane PostgresInstance claim. The developer never touches cloud credentials or raw RDS manifests.
3. Backstage: Integrating Catalog and Scaffolder
Backstage acts as the central developer portal. Using Backstage Software Templates, developers can initiate new microservices with built-in cloud infrastructure without leaving the UI.
Below is a Backstage Scaffolder Template snippet that generates a microservice repo containing the application boilerplate and the score.yaml file:
# template.yaml: Backstage Software Template
apiVersion: backstage.io/v1alpha1
kind: Template
metadata:
name: golang-microservice-template
title: Go Microservice with Managed PostgreSQL
description: Creates a Go microservice integrated with Crossplane-provisioned Postgres DB via Score spec.
spec:
owner: platform-team
type: service
parameters:
- title: Service Configuration
required:
- serviceName
- dbStorageSize
properties:
serviceName:
title: Service Name
type: string
default: order-processing
dbStorageSize:
title: Database Storage (GB)
type: integer
default: 50
steps:
- id: fetch-base
name: Fetch Template Code
action: fetch:template
url: ./template
values:
serviceName: ${{ parameters.serviceName }}
dbStorageSize: ${{ parameters.dbStorageSize }}
- id: publish-git
name: Publish to GitHub
action: publish:github
input:
allowedOwners: ['enterprise-org']
repoUrl: 'github.com?repo=' + parameters.serviceName + '&owner=enterprise-org'
- id: register-catalog
name: Register Component in Backstage
action: catalog:register
input:
catalogInfoUrl: 'https://github.com/enterprise-org/' + parameters.serviceName + '/blob/main/catalog-info.yaml'Enterprise Governance, Policy Enforcement, and GitOps
A resilient IDP requires strict runtime validation and zero manual drift. By implementing ArgoCD alongside Open Policy Agent (OPA) / Kyverno, platform teams can enforce regulatory compliance across the entire pipeline:
- Policy Enforcement at the Kubernetes API Layer: Kyverno policies inspect incoming Crossplane Composite Resources (XRs) to enforce mandatory tags (e.g.,
cost-center,owner) and restrict maximum storage limits. - Continuous Drift Reconciliation: Crossplane continually compares actual multi-cloud infrastructure state against the Kubernetes API state, automatically reverting unauthorized modifications in AWS or GCP.
- Secure Secret Injection: Secrets generated by Crossplane (e.g., RDS database credentials) are stored directly into HashiCorp Vault or Kubernetes Secrets, which are then dynamically mapped into the application Pod environment by the Score resource resolver.
Conclusion
Combining Backstage, Score, and Crossplane establishes a high-velocity, secure Internal Developer Platform. Backstage eliminates portal fragmentation, Score shields developers from Kubernetes and infrastructure verbosity, and Crossplane transforms Kubernetes into a universal multi-cloud control plane. This modern stack empowers application developers to self-serve complete stacks in minutes while enabling platform engineers to enforce security, compliance, and cost control across multi-cloud enterprise footprints.
