Crossplane lets you manage cloud resources, like databases and Kubernetes clusters, as if they were just more Kubernetes resources.

Let’s see it in action. Imagine you want to provision a managed Kubernetes cluster in AWS, but you don’t want to manually click around in the AWS console or write complex Terraform. Crossplane can do this for you.

First, you need Crossplane installed in your existing Kubernetes cluster. This is your "control plane" cluster.

helm install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace

Next, you need to tell Crossplane how to talk to your cloud provider. This is done through a ProviderConfig. For AWS, you’ll create a secret containing your AWS credentials and then a ProviderConfig resource that references that secret.

# aws-creds.yaml
apiVersion: v1
kind: Secret
metadata:
  name: aws-secret
  namespace: crossplane-system
type: Opaque
stringData:
  id: "YOUR_AWS_ACCESS_KEY_ID"
  secret: "YOUR_AWS_SECRET_ACCESS_KEY"

---
# aws-providerconfig.yaml
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: aws-secret
      key: id
    secretRef:
      namespace: crossplane-system
      name: aws-secret
      key: secret

Apply these:

kubectl apply -f aws-creds.yaml -f aws-providerconfig.yaml

Now, Crossplane knows how to provision AWS resources. You define a Composed resource, which is a custom Kubernetes object that represents the cloud resource you want. For an AWS EKS cluster, this would be an RDSInstance (for example, if you wanted a managed database).

Let’s provision an RDS instance. You’ll define an RDSInstance Custom Resource (CR) in your control plane cluster.

# rds-instance.yaml
apiVersion: rds.aws.crossplane.io/v1beta1
kind: RDSInstance
metadata:
  name: my-postgres-instance
spec:
  forProvider:
    region: us-east-1
    dbInstanceClass: db.t3.micro
    engine: postgres
    engineVersion: "13.7"
    allocatedStorage: 20 # in GB
    masterUsername: admin
    skipFinalSnapshot: true
  providerConfigRef:
    name: default

When you kubectl apply -f rds-instance.yaml, Crossplane sees this RDSInstance object. It looks at its ProviderConfig for AWS and uses your credentials to call the AWS API and create a managed PostgreSQL instance. The RDSInstance object in Kubernetes will be updated with the status of the actual AWS resource, like its endpoint and status.

The core problem Crossplane solves is the "snowflake" problem of infrastructure. Every team might provision their databases or Kubernetes clusters differently, leading to inconsistencies and operational overhead. Crossplane allows you to define these infrastructure components as Kubernetes CRDs. You can then use standard Kubernetes tools like kubectl and GitOps workflows to manage them.

The mental model is that your Kubernetes API becomes a universal control plane for all your infrastructure, not just your application workloads. You define a desired state for a cloud resource (like an RDS instance or a Kubernetes cluster), and Crossplane’s controllers ensure that the actual cloud resource matches that desired state. It reconciles the difference.

What most people don’t realize is how deeply Crossplane can integrate with your existing Kubernetes RBAC. You can grant a specific team permission to create RDSInstance objects in a particular namespace, and they can do so without ever needing direct AWS IAM credentials. Crossplane handles the secure translation of those Kubernetes permissions into cloud API calls.

The next step is to think about how to manage the lifecycle of these resources, particularly around upgrades and deletions.

Want structured learning?

Take the full Crossplane course →