Crossplane turns Kubernetes into a universal control plane for cloud infrastructure. Managed Resources map 1:1 to cloud API resources. Composite Resource Definitions (XRDs) create custom Kubernetes APIs from infrastructure building blocks. Compositions define how those custom resources translate to real cloud resources. Applications request databases and buckets via Kubernetes-native API calls — no Terraform CLI, no terraform apply, just kubectl apply. Claude Code generates Crossplane XRDs, Compositions, managed resource configs, and the claim objects that teams use to self-serve infrastructure.
CLAUDE.md for Crossplane Projects
## Crossplane Stack
- Version: Crossplane 1.17+ (installed in cluster via Helm)
- Providers: provider-aws-s3, provider-aws-rds, provider-aws-ec2, provider-kubernetes
- State: Kubernetes etcd (no separate state file like Terraform)
- Auth: IRSA (AWS), Workload Identity (GCP), pod identity (Azure)
- XRDs: one per platform capability (database, bucket, cache, queue)
- Compositions: per environment variant (dev=minimal, prod=HA)
- Claims: what teams submit — in app namespaces
- GitOps: ArgoCD manages all Crossplane objects declaratively
Provider Configuration
# crossplane/providers/provider-aws.yaml
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-rds
spec:
package: xpkg.upbound.io/upbound/provider-aws-rds:v1.14.0
runtimeConfigRef:
name: default
---
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: IRSA # IAM Roles for Service Accounts (recommended for EKS)
# crossplane/providers/provider-aws-rds.yaml
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-rds
spec:
package: xpkg.upbound.io/upbound/provider-aws-rds:v1.14.0
Composite Resource Definition (XRD)
# crossplane/xrds/xdatabase.yaml
# Defines the custom API that platform teams expose to developers
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xdatabases.platform.mycompany.com
spec:
group: platform.mycompany.com
names:
kind: XDatabase
plural: xdatabases
claimNames:
kind: Database # What teams create in their namespaces
plural: databases
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [parameters]
properties:
parameters:
type: object
required: [engine, size, name]
properties:
engine:
type: string
enum: [postgres, mysql]
description: Database engine
size:
type: string
enum: [small, medium, large]
description: "small=db.t3.micro, medium=db.t3.small, large=db.r6g.large"
name:
type: string
pattern: '^[a-z][a-z0-9-]{2,30}$'
highAvailability:
type: boolean
default: false
description: Enable Multi-AZ (required for production)
storageGb:
type: integer
minimum: 20
maximum: 1000
default: 100
status:
type: object
properties:
endpoint:
type: string
description: Database connection endpoint
port:
type: integer
secretRef:
type: object
properties:
name:
type: string
namespace:
type: string
Composition
# crossplane/compositions/database-postgres-aws.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: database-postgres-aws
labels:
provider: aws
engine: postgres
spec:
compositeTypeRef:
apiVersion: platform.mycompany.com/v1alpha1
kind: XDatabase
# Write connection details to a secret
writeConnectionSecretsToNamespace: crossplane-system
resources:
# 1. DB Subnet Group
- name: db-subnet-group
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: SubnetGroup
spec:
forProvider:
region: us-east-1
description: "Managed by Crossplane"
subnetIdSelector:
matchLabels:
access: private
providerConfigRef:
name: default
patches:
- type: FromCompositeFieldPath
fromFieldPath: metadata.name
toFieldPath: metadata.name
transforms:
- type: string
string:
fmt: "%s-subnet-group"
# 2. DB Instance
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
region: us-east-1
dbSubnetGroupNameSelector:
matchControllerRef: true # Pick subnet group from this composition
vpcSecurityGroupIdSelector:
matchLabels:
role: database
skipFinalSnapshot: true
autoMinorVersionUpgrade: true
backupRetentionPeriod: 7
deletionProtection: false
providerConfigRef:
name: default
writeConnectionSecretToRef:
namespace: crossplane-system
name: "" # Will be patched
patches:
# Map size param to instance class
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map:
small: db.t3.micro
medium: db.t3.small
large: db.r6g.large
# Wire engine
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.engine
toFieldPath: spec.forProvider.engine
# Wire high availability
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.highAvailability
toFieldPath: spec.forProvider.multiAz
# Wire storage
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.storageGb
toFieldPath: spec.forProvider.allocatedStorage
# Set secret name based on XR name
- type: FromCompositeFieldPath
fromFieldPath: metadata.name
toFieldPath: spec.writeConnectionSecretToRef.name
transforms:
- type: string
string:
fmt: "%s-credentials"
# Expose endpoint in status
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.endpoint
toFieldPath: status.endpoint
connectionDetails:
- fromFieldPath: status.atProvider.endpoint
name: endpoint
- fromFieldPath: status.atProvider.port
name: port
# 3. Create Kubernetes Secret in claim namespace
- name: secret-copier
base:
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
spec:
forProvider:
manifest:
apiVersion: v1
kind: Secret
metadata: {}
data: {}
Namespace-Scoped Claims
# teams/payments-team/database-claim.yaml
# Teams submit this — they never touch AWS console
apiVersion: platform.mycompany.com/v1alpha1
kind: Database
metadata:
name: payments-db
namespace: payments
spec:
parameters:
engine: postgres
size: medium
name: payments-db
highAvailability: true # Required for production
storageGb: 200
writeConnectionSecretToRef:
name: payments-db-credentials # Available in payments namespace
# teams/analytics-team/database-claim.yaml
apiVersion: platform.mycompany.com/v1alpha1
kind: Database
metadata:
name: analytics-db
namespace: analytics
spec:
parameters:
engine: postgres
size: large
name: analytics-db
highAvailability: false # Dev environment — save costs
storageGb: 500
writeConnectionSecretToRef:
name: analytics-db-credentials
S3 Bucket XRD and Composition
# crossplane/xrds/xbucket.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xbuckets.platform.mycompany.com
spec:
group: platform.mycompany.com
names:
kind: XBucket
plural: xbuckets
claimNames:
kind: Bucket
plural: buckets
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [parameters]
properties:
parameters:
type: object
required: [purpose]
properties:
purpose:
type: string
enum: [uploads, backups, artifacts, public-assets]
versioning:
type: boolean
default: false
lifecycleRuleDays:
type: integer
description: Delete objects older than N days (0 = no expiry)
default: 0
---
# crossplane/compositions/bucket-aws.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: bucket-aws
spec:
compositeTypeRef:
apiVersion: platform.mycompany.com/v1alpha1
kind: XBucket
resources:
- name: s3-bucket
base:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
spec:
forProvider:
region: us-east-1
forceDestroy: false
providerConfigRef:
name: default
patches:
- type: FromCompositeFieldPath
fromFieldPath: metadata.name
toFieldPath: metadata.name
transforms:
- type: string
string:
fmt: "mycompany-%s"
- name: bucket-versioning
base:
apiVersion: s3.aws.upbound.io/v1beta1
kind: BucketVersioning
spec:
forProvider:
region: us-east-1
versioningConfiguration:
- status: "" # Will be patched
bucketSelector:
matchControllerRef: true
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.versioning
toFieldPath: spec.forProvider.versioningConfiguration[0].status
transforms:
- type: convert
convert:
toType: string
- type: map
map:
"true": Enabled
"false": Suspended
- name: bucket-encryption
base:
apiVersion: s3.aws.upbound.io/v1beta1
kind: BucketServerSideEncryptionConfiguration
spec:
forProvider:
region: us-east-1
bucketSelector:
matchControllerRef: true
rule:
- applyServerSideEncryptionByDefault:
- sseAlgorithm: aws:kms
Observing Composition Status
# Check claim status
kubectl get database -n payments
# NAME SYNCED READY CONNECTION-SECRET AGE
# payments-db True True payments-db-credentials 5m
# Check the composite resource
kubectl get xdatabase
# NAME SYNCED READY COMPOSITION AGE
# payments-payments-db True True database-postgres-aws 5m
# Check underlying managed resources
kubectl get instance.rds.aws.upbound.io
# NAME READY SYNCED EXTERNAL-NAME ID
# payments-payments-db True True payments-payments-db ...
# View events if something is wrong
kubectl describe xdatabase payments-payments-db
kubectl describe instance.rds.aws.upbound.io payments-payments-db
For the Kubernetes cluster that hosts Crossplane and the applications consuming its infrastructure claims, see the Kubernetes guide for deployment patterns. For the GitOps workflow that manages Crossplane manifests declaratively, the GitOps and ArgoCD guide covers ArgoCD application management. The Claude Skills 360 bundle includes Crossplane skill sets covering XRD design, Composition authoring, and platform API patterns. Start with the free tier to try Crossplane configuration generation.