Crossplane turns infrastructure into a control plane, and that is the whole argument

A pipeline runs when someone triggers it. A controller reconciles continuously, which fixes drift by construction. What you pay is another control plane to operate and a harder debugging story.

The interesting claim about Crossplane is not that it manages infrastructure from Kubernetes. It is that it changes the execution model.

A pipeline applies a desired state when someone runs it. Between runs, reality can diverge and nothing notices until the next plan, which is why drift detection is a scheduled job you have to remember to build. A controller runs a reconciliation loop: it compares desired against actual continuously and corrects the difference. Someone changing a security group in the console has it changed back within the reconciliation interval.

That is a genuine difference in behaviour, and whether it is worth the cost depends on what you are trying to build.

The two things it actually offers

Continuous reconciliation. Drift is corrected rather than reported. For an estate where console changes during incidents are a recurring problem, this is the feature.

Composition, which is the more interesting half. You define a custom resource representing something your organisation offers, such as a database or an environment, and a composition that expands it into the underlying cloud resources. Application teams then request the abstraction without knowing or caring what it expands into.

That second part is what makes this platform engineering rather than an alternative Terraform. The abstraction is a real Kubernetes API, so it has a schema, validation, status conditions and access control, and teams consume it with the tools they already use. A self-service database request becomes a manifest in a team's repository, reconciled by the platform.

Where it fits, and where it does not

It fits when you are building an internal platform with a self-service surface for several teams, when you already run Kubernetes as your primary operational substrate, when drift from manual changes is a real recurring problem, and when you want the abstraction to be an API rather than a module people copy.

It does not fit when you have one team managing one estate, where a pipeline is simpler and better understood. When your infrastructure is mostly foundational and changes rarely, since reconciliation solves a problem you do not have. When you need a service or a property the provider does not yet cover, which is a real constraint. Or when nobody can own another control plane.

The honest comparison is not Crossplane against Terraform, it is Crossplane against Terraform plus GitOps. A pipeline triggered by a merge, with a scheduled drift check, gets you most of the way, and the patterns in Argo CD in production already give you reconciliation for everything running in the cluster.

What you take on

Another control plane, in the critical path. The thing that manages your infrastructure now runs inside the infrastructure it manages. Think through the bootstrap problem carefully: how do you create the cluster that runs Crossplane, and what happens if that cluster is lost. The common answer is a small, separate, well-protected management cluster, which is another thing to run.

Provider maturity varies significantly. Coverage for common resources is good. Coverage for newer services, preview features and obscure properties is patchier than the equivalent Terraform provider, which has years more accumulated surface area. Check the specific resources you need before committing, not the headline support.

Debugging is harder. When a pipeline fails you read the output. When a controller fails, you are reading status conditions on nested resources, working out which managed resource is not ready and why, and correlating with controller logs. It is a different skill and the feedback loop is slower.

Composition is powerful and can become unreadable. A composition that patches values between resources through transforms is genuinely hard to follow. The newer composition functions approach improves this by letting you write logic properly, which is better and adds another component. Keep compositions boring for the same reason you keep modules boring, as argued in module design.

There is no plan. This is the loss people feel most. You do not get a reviewable description of what will change before it changes, which removes the artefact that makes reading a Terraform plan the safety net it is. You get a diff of a manifest and then a controller that acts. For high-blast-radius resources that is a real regression, and it is why deletion policies matter enormously here.

Practical rules if you adopt it

Set the deletion policy deliberately on everything. A custom resource deleted by accident can delete the database it represents. Orphan rather than delete for anything stateful, and use the same instinct as the reclaim policy discussion in stateful workloads in Kubernetes.

Keep the management cluster small, separate and boring. It should not run application workloads.

Split the estate. Foundational, slow-moving infrastructure such as networks, accounts and the management cluster itself stays in Terraform, where the plan and the review exist. Application-adjacent, frequently-requested infrastructure such as databases, buckets and queues moves to compositions, where self-service is the point. Most successful adoptions we have seen are hybrids, not replacements.

Version compositions and treat a composition change as a change to every claim using it, because it is. Roll it out to a non-production claim first.

Monitor the controllers. A reconciliation loop that has stopped is silent, and silence looks identical to everything being fine, which is the same trap described in alerts people answer.

The things people forget

  • Credentials for the whole estate live in one cluster. That cluster is now one of the most sensitive things you run, and the secret handling in a Kubernetes Secret is base64 applies with force.
  • Reconciliation costs API calls. A large estate with a short interval generates constant traffic to cloud APIs and can hit rate limits.
  • Importing existing resources is possible and fiddly. Plan the migration path before you need it.
  • Composition changes propagate immediately. There is no staged rollout unless you build one.
  • Status is not always trustworthy. A resource reporting ready may have been created with a property you did not intend, because the provider silently ignored an unsupported field.

What to do this week

Before evaluating the tool, measure the problem. Run a plan against every production workspace and count the resources that differ from the code. If the answer is zero, drift is not your problem and continuous reconciliation is solving something you do not have. If it is a long list, you have found the argument, and a scheduled drift check might still be the cheaper fix. We run that measurement in the platform phase of a cloud engagement.

ConsultorIA

Want this done on your cloud?

A ten-day read-only assessment is free, and Skyline lets you see your estate on a map before you write to us.

Related articles

Modules people reuse instead of copying

The two failures are a module that wraps one resource and adds nothing, and a module that does everything and nobody dares change. A minimal interface, safe defaults and honest versioning are what separate them.