Backstage isn’t just a catalog of services; it’s a dynamic facade for your entire engineering ecosystem, and when paired with Crossplane, it becomes the single pane of glass for provisioning and managing cloud infrastructure.
Imagine a developer needing a new PostgreSQL instance for their application. Without a portal, this involves tickets, Slack messages, waiting for SREs, and manually piecing together CloudFormation or Terraform. With Backstage and Crossplane, the developer navigates to their service’s page in Backstage, clicks "Provision PostgreSQL," fills out a simple form (like desired instance size and version), and a few minutes later, a fully provisioned, secure PostgreSQL database is ready, complete with connection strings and credentials automatically injected into their application’s secrets.
Here’s how it works under the hood. Crossplane acts as a Kubernetes control plane for your cloud resources. You define your desired infrastructure state using Crossplane’s Custom Resource Definitions (CRDs) – think PostgreSQLInstance, RDSInstance, or GCPCloudSQLInstance. These CRDs are the "blueprints" for cloud resources. When you apply one of these CRDs to your Kubernetes cluster, Crossplane’s controllers, which are essentially specialized Kubernetes operators, translate that CRD into the native API calls for your cloud provider (AWS, Azure, GCP, etc.). They then manage the lifecycle of that resource, ensuring it matches the desired state defined in the CRD.
Backstage, on the other hand, provides the user interface and the catalog. It ingests metadata about your services, infrastructure, and teams from various sources. For Crossplane integration, you’ll typically use Backstage’s Software Catalog to define your infrastructure resources as entities. You can then create custom frontend components within Backstage to interact with Crossplane. For instance, a "Provisioner" plugin in Backstage can display a form that dynamically generates Crossplane CRDs based on user input. When the form is submitted, Backstage sends this CRD to your Kubernetes cluster where Crossplane picks it up and starts provisioning.
Let’s look at a practical example. Suppose you want to allow developers to provision AWS RDS instances.
First, ensure Crossplane is installed in your Kubernetes cluster and configured with an AWS provider. Your ProviderConfig might look like this:
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-credentials
key: credentials
And your aws-credentials secret would contain your AWS access key ID and secret access key.
Next, you define a Crossplane composite resource that abstracts the RDS instance. This composite resource, let’s call it XPostgreSQLInstance, might be composed of a managed RDSInstance and a DBSubnetGroup.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqlinstances.database.example.com
spec:
group: database.example.com
names:
kind: XPostgreSQLInstance
plural: xpostgresqlinstances
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
storageGB:
type: integer
instanceClass:
type: string
required:
- parameters
connectionSecretKeys:
- username
- password
- endpoint
And the composition that defines how XPostgreSQLInstance is built:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: rds-postgres-composition
spec:
compositeTypeRef:
apiVersion: database.example.com/v1alpha1
kind: XPostgreSQLInstance
resources:
- name: rdsinstance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
allocatedStorage: 20 # Default storage
engine: Postgres
engineVersion: "14.3"
dbSubnetGroupName: default-subnet-group # Assume this exists
publiclyAccessible: false
skipFinalDestroy: true
tags:
managed-by: crossplane
environment: staging
patches:
- fromFieldPath: spec.parameters.storageGB
toFieldPath: spec.allocatedStorage
- fromFieldPath: spec.parameters.instanceClass
toFieldPath: spec.class
In Backstage, you’d represent this XPostgreSQLInstance as an entity in your catalog-info.yaml:
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
name: rds-postgres-provisioner
description: Provision AWS RDS PostgreSQL instances
spec:
type: crossplane-provisioner
owner: team-infra
Then, you’d build a Backstage plugin. This plugin would have a component that renders a form. When a user fills in storageGB and instanceClass and clicks "Provision," the plugin constructs an XPostgreSQLInstance CRD like this:
apiVersion: database.example.com/v1alpha1
kind: XPostgreSQLInstance
metadata:
name: my-app-db
spec:
parameters:
storageGB: 100
instanceClass: db.t3.medium
This CRD is then applied to your Kubernetes cluster via the Backstage backend, triggering Crossplane to create the RDS instance. Backstage can then display the status of this provisioning request by watching the XPostgreSQLInstance object in Kubernetes.
The real magic happens when you start connecting these provisioned resources back to your application entities in Backstage. You can use Backstage’s dependsOn and provides relationships to link your application to its database. This means when a developer views their application in Backstage, they see not just the code repository and CI/CD pipeline, but also the live, provisioned infrastructure it relies on.
One aspect often overlooked is how Crossplane manages secrets. When a Crossplane-provisioned resource, like an RDS instance, is created, it can automatically output connection details (username, password, endpoint) into a Kubernetes secret. Backstage can then dynamically read this secret and inject it into the application’s CI/CD pipeline or deployment configuration, fully automating the connection process without manual intervention. This means developers don’t just get a database; they get a database that’s immediately usable by their application.
The next step is to integrate Crossplane’s observability features into Backstage, allowing developers to see metrics and logs for their provisioned infrastructure directly within the portal.