Building Enterprise Developer Platforms with Backstage, Score, and Crossplane
Building Enterprise Internal Developer Platforms with Backstage, Score, and Crossplane
Modern enterprise engineering organizations face severe developer cognitive overload. As cloud-native architectures grow in complexity across AWS, GCP, and Azure, software engineers are routinely required to navigate Kubernetes manifests, Terraform configurations, IAM policies, and cloud-vendor-specific APIs just to deploy a basic microservice with a managed database. The solution is an Internal Developer Platform (IDP) that abstracts lower-level infrastructure, enforces platform governance, and provides Golden Paths for self-service capability.
This article details the architecture and implementation of a next-generation Enterprise IDP built on three foundational pillars:
- Backstage: The developer portal frontend, software catalog, and template scaffolding engine.
- Score: A cloud-agnostic workload specification standard that decouples application requirements from target environment implementations.
- Crossplane: A Kubernetes-native control plane engine that translates high-level infrastructure claims into enterprise-ready multi-cloud resources.
Architecture Overview
The IDP architecture separates concerns into clear layers: UI/Discovery (Backstage), Developer Specification (Score), Infrastructure Orchestration (Crossplane), and Deployment Control (GitOps with ArgoCD/Flux).
+-----------------------------------------------------------------------------------+
| DEVELOPER PORTAL |
| +---------------------------------------------------------------------------+ |
| | Backstage (Software Catalog, Scaffolder, Technical Documentation) | |
| +---------------------------------------------------------------------------+ |
+------------------------------------------+----------------------------------------+
|
Scaffolds Repo & Specs
v
+-----------------------------------------------------------------------------------+
| DEVELOPER WORKLOAD SPEC |
| +---------------------------------------------------------------------------+ |
| | Score Spec (score.yaml) -> score-k8s / score-compose | |
| +---------------------------------------------------------------------------+ |
+------------------------------------------+----------------------------------------+
|
Generates Manifests / Claims
v
+-----------------------------------------------------------------------------------+
| GITOPS & CONTROL PLANE ENGINE |
| +------------------------------------+ +-----------------------------------+ |
| | GitOps Repository (ArgoCD / Flux) | | Crossplane Universal Control Plane | |
| +------------------------------------+ +-----------------------------------+ |
+------------------------------------------+----------------------------------------+
|
Provisions Managed Resources
v
+-----------------------------------------------------------------------------------+
| MULTI-CLOUD INFRASTRUCTURE |
| +----------------------+ +-----------------------+ +--------------------+ |
| | AWS (RDS, EKS, S3) | | GCP (CloudSQL, GKE) | | Azure (Database) |
| +----------------------+ +-----------------------+ +--------------------+ |
+-----------------------------------------------------------------------------------+Component 1: Universal Control Plane with Crossplane
Crossplane turns Kubernetes clusters into universal control planes. Platform engineers define custom abstractions called Composite Resource Definitions (XRDs) and bind them to cloud implementations via Compositions. Developers consume these abstractions without knowing cloud vendor specifics.
Defining a CompositeResourceDefinition (XRD)
Below is an enterprise XRD defining a standard managed database resource claim (XPostgreSQLInstance):
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqlinstances.database.platform.enterprise.io
spec:
group: database.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
databaseName:
type: string
engineVersion:
type: string
default: "15"
required:
- databaseName
required:
- parametersAWS RDS Infrastructure Composition
Platform engineers implement the XRD for specific cloud providers using a Crossplane Composition. The manifest below routes requests to AWS RDS with encrypted storage and Automated Backup configurations:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: rds-postgres-production
labels:
provider: aws
environment: production
spec:
compositeTypeRef:
apiVersion: database.platform.enterprise.io/v1alpha1
kind: XPostgreSQLInstance
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
region: us-west-2
instanceClass: db.t4g.medium
allocatedStorage: 20
engine: postgres
skipFinalSnapshot: true
storageEncrypted: true
publiclyAccessible: false
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.storageGB
toFieldPath: spec.forProvider.allocatedStorage
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.engineVersion
toFieldPath: spec.forProvider.engineVersionComponent 2: Developer Workload Specification with Score
While Crossplane manages infrastructure resources, Score (score.dev) provides a clean, platform-agnostic developer specification standard. Instead of writing verbose Kubernetes deployment YAMLs or Helm values, developers describe what their application needs to run in a score.yaml file.
apiVersion: score.dev/v1b1
metadata:
name: payment-service
containers:
web:
image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/payment-service:v2.1.0
variables:
DB_HOST: "${resources.db.host}"
DB_PORT: "${resources.db.port}"
DB_NAME: "${resources.db.name}"
DB_USER: "${resources.db.username}"
DB_PASSWORD: "${resources.db.password}"
resources:
db:
type: postgres
params:
extensions:
- uuid-osspDuring automated CI/CD pipeline execution, the score-k8s CLI tool transforms this platform-agnostic definition into native Kubernetes resources and Crossplane Claims (`PostgreSQLInstance`), binding parameters automatically.
Component 3: Backstage Orchestration & Scaffolder Templates
Backstage serves as the entry point for software developers. Through Backstage Software Templates, engineers can spin up a new repository containing the application code, `score.yaml` specification, and GitOps integration files in seconds.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: enterprise-java-service
title: Java Microservice with Managed Database
description: Scaffolds a Production-Ready Spring Boot Service integrated with Crossplane & Score
spec:
owner: platform-team
type: service
parameters:
- title: Service Details
required:
- name
- owner
properties:
name:
title: Service Name
type: string
pattern: '^[a-z0-9-]+$'
owner:
title: Owner Team
type: string
ui:field: OwnerPicker
- title: Database Requirements
properties:
enableDatabase:
title: Provision Cloud Database?
type: boolean
default: true
storageGB:
title: Database Storage (GB)
type: integer
default: 20
steps:
- id: fetch-template
name: Fetch Base Skeleton
action: fetch:template
input:
url: ./skeleton
values:
name: ${{ parameters.name }}
owner: ${{ parameters.owner }}
enableDatabase: ${{ parameters.enableDatabase }}
storageGB: ${{ parameters.storageGB }}
- id: publish-github
name: Publish to GitHub
action: publish:github
input:
allowedHosts: ['github.com']
repoUrl: 'github.com?repo=${{ parameters.name }}&owner=enterprise-org'
- id: register-catalog
name: Register Component in Backstage
action: catalog:register
input:
catalogInfoUrl: 'https://github.com/enterprise-org/${{ parameters.name }}/blob/main/catalog-info.yaml'End-to-End Execution Flow
The operational workflow proceeds through four seamlessly integrated steps:
- Self-Service Request: A developer selects the template in Backstage, specifies required storage, and clicks generate.
- Repository Scaffolding: Backstage generates the service codebase, injects `score.yaml`, and creates the Crossplane Claim manifest in the GitOps infrastructure repository.
- Control Plane Synchronization: ArgoCD synchronizes the GitOps repository with the control plane cluster. Crossplane interceptors read the `PostgreSQLInstance` claim, match the appropriate `Composition`, and provision an AWS RDS instance.
- Dynamic Secret & Endpoint Binding: Crossplane writes generated credentials directly to Kubernetes Secrets. The `score-k8s` operator mounts these secrets as environment variables inside the running workload pod.
Enterprise Governance and Day-2 Operations
Building an enterprise-ready IDP requires addressing security, governance, and drift management:
- RBAC & Identity Federation: Implement OIDC bridging via Keycloak or Okta. Developers only hold permissions to write Score files or request claims within their team namespace. Crossplane operators run under privileged cloud IAM roles via AWS IRSA or GCP Workload Identity.
- Secret Management Integration: Never store raw database passwords inside Kubernetes secret objects permanently. Integrate External Secrets Operator (ESO) with HashiCorp Vault or AWS Secrets Manager to automatically sync Crossplane-generated credentials.
- Policy Enforcement: Use Kyverno or Open Policy Agent (OPA) to validate Crossplane Compositions and Score files prior to deployment, enforcing encryption, tagging standards, and resource limits.
By pairing Backstage for developer experience, Score for workload abstraction, and Crossplane for multi-cloud infrastructure orchestration, enterprises achieve full developer self-service while enforcing rigid platform security and governance standards.
