Crossplane lets developers provision and manage their own databases without needing to ask a central ops team for help.
Here’s a database being provisioned on AWS RDS using Crossplane:
apiVersion: sql.aws.upbound.io/v1beta1
kind: DBInstance
metadata:
name: my-app-db
spec:
forProvider:
region: us-east-1
allocatedStorage: 20
engine: postgres
engineVersion: "14.3"
instanceClass: db.t3.micro
parameterGroupName: default.postgres14
skipFinalSnapshot: true
providerConfigRef:
name: aws-provider-default
This DBInstance resource is a Crossplane Composed Resource. When applied to a Kubernetes cluster with Crossplane installed and configured with an AWS provider, Crossplane will call the AWS API to create an RDS instance. The providerConfigRef tells Crossplane which specific AWS credentials and configuration to use. The spec.forProvider block directly mirrors the parameters you’d pass to the AWS RDS API.
The core problem Crossplane solves is the friction between developers who need infrastructure and operations teams who manage it. Traditionally, developers submit tickets, wait for provisioning, and then get credentials. This is slow and creates bottlenecks. Crossplane shifts this by defining infrastructure as Kubernetes resources. Developers simply create a DBInstance (or similar resource for other managed services) in their own Kubernetes namespaces.
Internally, Crossplane uses a control loop. When you create a DBInstance resource, Crossplane’s sql.aws.upbound.io controller watches for it. It sees the desired state (spec) and compares it to the actual state of the world (what’s actually running in AWS). If they don’t match, it calls the AWS API (via the AWS provider) to reconcile the difference, creating the RDS instance. Once created, Crossplane updates the DBInstance resource with its status, including connection details.
The "Claims" part is crucial for self-service. Developers don’t directly manage the cloud provider resources (DBInstance in the example above). Instead, they create a DBClaim resource, which is a higher-level abstraction.
apiVersion: database.example.com/v1alpha1
kind: PostgresInstance
metadata:
name: my-app-db-claim
namespace: my-app-dev
spec:
parameters:
storageGB: 20
instanceType: "db.t3.micro"
This PostgresInstance is a custom resource defined by a Crossplane Composition. The Composition is an opinionated template that dictates how a PostgresInstance claim should be fulfilled. It specifies that a PostgresInstance claim should result in the creation of an AWS DBInstance resource, defining the exact configuration of that DBInstance based on the claim’s parameters. The Composition also defines which ProviderConfig to use.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: postgresinstances.database.example.com
spec:
compositeTypeRef:
apiVersion: database.example.com/v1alpha1
kind: PostgresInstance
resources:
fromComposite:
- name: rds-dbinstance
base:
apiVersion: sql.aws.upbound.io/v1beta1
kind: DBInstance
spec:
forProvider:
region: us-east-1
engine: postgres
engineVersion: "14.3"
skipFinalSnapshot: true
providerConfigRef:
name: aws-provider-default
patches:
- fromFieldPath: spec.parameters.storageGB
toFieldPath: spec.forProvider.allocatedStorage
- fromFieldPath: spec.parameters.instanceType
toFieldPath: spec.forProvider.instanceClass
This Composition says: "When someone creates a PostgresInstance claim, create an AWS DBInstance. Map the spec.parameters.storageGB from the claim to the spec.forProvider.allocatedStorage of the DBInstance, and map spec.parameters.instanceType to spec.forProvider.instanceClass." The Composition also injects a fixed region, engine, engineVersion, and skipFinalSnapshot value, ensuring consistency and adherence to organizational policies. Developers requesting a PostgresInstance don’t need to know about DBInstance or AWS RDS specifics; they just ask for a Postgres database with certain parameters.
The magic of the Composition is that it abstracts away the underlying cloud provider resource. The operations team defines the Compositions, setting guardrails and best practices. Developers consume these Compositions by creating Claims. This decouples the developer’s workflow from the operational complexities of managing cloud infrastructure. The connection details for the created database are then exposed as a Kubernetes Secret, which the developer’s application can easily reference.
The ProviderConfig itself is a static resource that holds the cloud provider credentials and region information. It’s typically managed by the operations team and referenced by Compositions.
When a developer creates a PostgresInstance claim in their namespace, Crossplane’s claim-to-composite controller matches the claim to a Composition based on compositeTypeRef. It then generates a Composed Resource (the AWS DBInstance in this case) based on the Composition’s template and the claim’s parameters. This Composed Resource is created in the same namespace as the claim. The composite-to-provider controller then reconciles this Composed Resource with the actual cloud provider resource.
The most surprising thing is how Compositions, which are themselves Kubernetes resources, can dynamically generate other Kubernetes resources based on input parameters. This dynamic templating allows for a rich set of abstractions to be built on top of raw cloud provider APIs, all managed within the familiar Kubernetes control plane.
The next step is to explore how to manage external resources like DNS records or load balancers that are also required for your database to be accessible.