Crossplane Compositions: Building Your Own Internal Cloud API
Crossplane turns Kubernetes into a control plane for any cloud. Compositions let you offer self-service infra to devs. Here's how the pieces fit together.
- #iac
- #crossplane
- #kubernetes
- #platform-engineering
- #cloud
- #self-service
Most infrastructure-as-code runs as a job: a pipeline executes Terraform or Pulumi, applies a change, and exits. Crossplane takes a fundamentally different stance — it makes infrastructure a set of Kubernetes resources that a controller continuously reconciles toward desired state, forever. Drift gets corrected automatically because reconciliation never stops.
The headline feature, and the reason platform teams care, is Compositions: the ability to bundle a pile of cloud resources behind a single, simple custom resource that your developers can self-serve. Here’s how to think about building one.
The control-plane mental model
Crossplane installs into a Kubernetes cluster and adds providers — for AWS, GCP, Azure, and hundreds of other services. Each provider exposes cloud resources as Kubernetes CRDs called Managed Resources. An RDS instance becomes a Kubernetes object:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
metadata:
name: app-db
spec:
forProvider:
region: us-east-1
instanceClass: db.t3.micro
engine: postgres
allocatedStorage: 20
providerConfigRef:
name: default
kubectl apply this and Crossplane provisions the RDS instance, then watches it forever. Delete the resource and it deprovisions. The cluster’s etcd is now your source of truth, and the controller is your apply loop. That continuous reconciliation is the philosophical difference from run-and-exit IaC.
The problem Compositions solve
Raw Managed Resources are too low-level to hand to application developers. A “database” in practice means an RDS instance plus a subnet group, a parameter group, a security group, and a secret. You don’t want every dev assembling those correctly.
Compositions let the platform team define that bundle once and expose it as a clean abstraction. Three pieces work together:
- CompositeResourceDefinition (XRD) — defines the simple API you offer, e.g. a
PostgreSQLInstancewith justsizeandversionfields. - Composition — the recipe mapping that simple API to the full set of Managed Resources.
- Claim — what developers actually create: a namespaced, dead-simple request.
Defining the developer-facing API (XRD)
The XRD is the contract. Keep it ruthlessly minimal — expose only what a developer should decide:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqlinstances.platform.example.org
spec:
group: platform.example.org
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:
size:
type: string
enum: ["small", "medium", "large"]
version:
type: string
required: ["size"]
Notice the size is an enum of t-shirt sizes, not a raw instance class. You’re encoding judgment into the API so developers can’t pick a db.r5.24xlarge by accident.
The Composition: where the real infra lives
The Composition maps that small API onto the full resource set and patches values across them:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: postgres.aws.platform.example.org
spec:
compositeTypeRef:
apiVersion: platform.example.org/v1alpha1
kind: XPostgreSQLInstance
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
engine: postgres
allocatedStorage: 20
patches:
- fromFieldPath: spec.parameters.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map:
small: db.t3.micro
medium: db.t3.medium
large: db.r5.large
The map transform turns small into db.t3.micro. The platform team owns this mapping; developers never see it. Add the subnet group, parameter group, and security group as sibling entries in the same resources list and you’ve encapsulated the entire “give me a database” workflow.
What developers actually write
After all that platform work, the developer experience is a few lines:
apiVersion: platform.example.org/v1alpha1
kind: PostgreSQLInstance
metadata:
name: orders-db
namespace: team-payments
spec:
parameters:
size: medium
writeConnectionSecretToRef:
name: orders-db-conn
They kubectl apply it, wait, and get a connection secret in their namespace. No cloud console, no Terraform, no ticket to the platform team. That’s the self-service payoff — and it’s why Crossplane shows up in nearly every serious platform-engineering conversation now.
Honest trade-offs
Crossplane is powerful but not free:
- The YAML patching is verbose and awkward. Complex transforms get unwieldy fast. Newer Compositions support functions (including Pythonic pipelines) that make this far nicer — prefer them over raw patch-and-transform for anything non-trivial.
- You’re running a control plane. That’s a stateful, critical Kubernetes workload your platform team now operates. The cluster going down means your infra API goes down.
- Debugging spans two systems. When provisioning fails, the answer might be in a Crossplane event, a provider log, or the cloud’s own error — more places to look than a single Terraform apply.
Where AI helps
The verbose, repetitive XRD-and-Composition YAML is exactly the kind of boilerplate AI handles well, while you supply the architectural judgment.
- Generating the XRD schema from a plain description: “an XRD exposing a Redis cache with size and an engine-version field, sizes small/medium/large.”
- Writing patch-and-transform blocks, which are fiddly and easy to typo. Describe the source and target field paths and the mapping, and let the model produce the YAML.
- Explaining a failed reconcile. Paste the composite resource’s events and the model is good at pointing to the misconfigured patch or missing provider config.
The model doesn’t know your org’s t-shirt-size mapping or security baseline, so treat its output as scaffolding you review against your standards. We maintain a set of IaC prompts for platform and Crossplane work.
The bigger picture
Compositions are how a platform team turns “infrastructure” into a product with an API. You absorb the complexity once — the resource bundles, the sane defaults, the guardrails — and hand developers a button. When it works, the day-to-day infra ticket queue largely disappears, because the common requests became self-service. That’s the promise of platform engineering, and Crossplane Compositions are one of the cleaner ways to deliver it.
AI-generated Crossplane manifests are assistive, not authoritative. Validate XRDs and Compositions in a non-production control plane before offering them to developers.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.