DEV Community

疏影
疏影

Posted on

Crossplane Composition vs Helm: When Each Wins

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 -json and rendered Kubernetes manifests
  • A CI job that ran terraform apply and helm upgrade in 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 apply instead 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)