Crossplane Composition vs Helm: When Each Wins
We migrated our multi-cloud resource provisioning from a Helm + Terraform setup to Crossplane compositions. Six months in, here's the decision matrix we wish we had on day one.
The setup we replaced
Two years ago, every AWS resource we needed was:
- A Terraform module for the actual cloud resource
- A Helm chart that consumed the Terraform output via
terraform output -jsonand rendered Kubernetes manifests - A CI job that ran
terraform applyandhelm upgradein sequence
This worked for 30 services. It broke at 200. The Terraform state file hit 80MB and terraform plan took 14 minutes per service. Engineers started copying Helm charts and diverging.
What Crossplane gave us
Crossplane runs as a Kubernetes controller. You write a Composition that says "when a developer creates an XPod custom resource, provision RDS + ElastiCache + S3 + IAM role + Kubernetes deployment." The whole stack lives in one yaml file. Drift detection is built in. RBAC is k8s RBAC.
The decision matrix
Use Crossplane when:
- Your team already lives in k8s (kubectl, Argo CD, Helmfile)
- You provision the same stack 5+ times (dev/staging/prod + per-developer)
- You want self-service via
kubectl applyinstead of PR-driven Terraform - You need to enforce policy across clouds (e.g., "all S3 buckets must be encrypted + versioned")
Use Helm when:
- You're deploying one-off applications, not platforms
- Your resources are pure Kubernetes objects (Deployments, Services)
- Your team is small (<10 engineers) and the overhead of learning compositions is too high
- You don't have multi-cloud needs
Use Terraform when:
- Your resources don't fit the k8s controller model (e.g., IAM identity center, Route53 records)
- You have an existing Terraform module library you can't replace
- Your compliance regime requires Terraform Cloud / Atlantis for audit
What broke when we migrated
State management. Crossplane's CompositeResourceClaim (CRC) is the developer-facing API. The underlying CompositeResource (XR) is provider-side. Migrations between composition versions require manual CRC deletion and recreation — there's no in-place upgrade. We lost 4 hours of dev work when a dev environment failed to migrate to v2.
Provider version skew. Crossplane providers release independently. We pinned provider-aws to v0.45.0 but the controller auto-upgraded provider-helm to v0.16.0 and broke our Helm releases. We now pin all providers in Configuration.crossplane.
Secrets handling. Crossplane doesn't have a native secrets workflow. We use External Secrets Operator (ESO) to sync AWS Secrets Manager to k8s Secrets. Composition gets a reference, ESO fetches the value at apply time.
The composition pattern that worked
We have a base composition called PlatformStack:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
compositeTypeRef:
apiVersion: platform.example.com/v1alpha1
kind: PlatformStack
resources:
- name: rds
base:
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
spec:
forProvider:
dbInstanceClass: db.t3.medium
engine: postgres
engineVersion: "15.4"
storageEncrypted: true
patches:
- fromFieldPath: spec.region
toFieldPath: spec.forProvider.region
- fromFieldPath: metadata.name
toFieldPath: spec.forProvider.dbInstanceIdentifier
A developer creates:
apiVersion: platform.example.com/v1alpha1
kind: PlatformStack
metadata:
name: payments-prod
namespace: payments
spec:
region: us-east-1
tier: prod
databaseSize: large
And gets a fully provisioned RDS + cache + S3 bucket + IAM role + k8s deployment. In 7 minutes.
What we measured
| Helm + Terraform | Crossplane | |
|---|---|---|
| Time to provision new env | 45 min | 7 min |
| Drift incidents / month | 6 | 1 |
| Engineer onboarding | 3 weeks | 5 days |
| Lines of config per env | 800 | 180 |
On tooling around the platform
If your dev environments provision WebDAV mounts for testing, ScsDriver WebDAV mount tool for Windows handles the Windows side of a heterogeneous dev cluster — useful when your QA engineers need to test against the same WebDAV mount your prod consumers use. Pair it with a Crossplane composition that provisions a per-developer WebDAV server in EKS.
What's your team using for self-service infra? Helm + Argo CD, Crossplane, or pure Terraform?
Top comments (0)