// Stay in touch?
Products
Encore CloudEncore Cloud
Encore.tsEncore.ts
Encore.goEncore.go
PricingPricing
Book a DemoBook a Demo
Use Cases
AI-Powered DevelopmentAI-Powered Development
Event-Driven SystemsEvent-Driven Systems
Distributed SystemsDistributed Systems
Case StudiesCase Studies
ShowcaseShowcase
Resources
DocsDocs
InstallInstall
Example AppsExample Apps
Demo videoDemo video
ArticlesArticles
GitHub ReleasesGitHub Releases
Systems Operational
Company
About UsAbout Us
Swag ShopSwag Shop
ContactContact
JobsJobs
PressPress
TermsTerms
Privacy PolicyPrivacy Policy
Data Processing AgreementData Processing Agreement
Enterprise SLAEnterprise SLA
Encore
© 2026 EncoreAll rights reserved
© 2026 Encore All Rights Reserved
GitHubDiscordYouTube

Crossplane vs Terraform in 2026

A continuously reconciling control plane inside Kubernetes versus a one-shot CLI apply

07/08/26
10 Min Read
Ivan Cernja
07/08/26

Crossplane vs Terraform in 2026

A continuously reconciling control plane inside Kubernetes versus a one-shot CLI apply

Ivan Cernja
10 Min Read

The decision between Crossplane and Terraform rarely comes down to which one can create an RDS instance, because both can, from declarative configuration. It comes down to how you want provisioning to run: as a control plane that lives inside a Kubernetes cluster and reconciles your infrastructure on a loop forever, or as a CLI you invoke to compute a diff and apply it once.

That single architectural choice drives everything downstream: the learning curve, how much you operate day to day, how drift is handled, and who on your team ends up owning the tool. This guide walks through the difference with real configuration for both, then gets specific about which teams each one fits.

The Core Architectural Difference

Terraform is a binary you run. You write HCL, run terraform plan to see the diff between your configuration and a state file, and run terraform apply to make the cloud match. When the command exits, nothing is running. Terraform has no opinion about what happens to your infrastructure between applies. If someone changes a security group by hand at 2am, Terraform does not know until the next time you run plan.

Crossplane is the opposite shape. You install it into a Kubernetes cluster, where it runs as a set of controllers. You express infrastructure as Kubernetes resources, and Crossplane's controllers watch those resources and continuously reconcile the real cloud state toward them. If someone deletes that RDS instance out of band, Crossplane notices within its reconciliation interval and recreates it. There is no "apply" step you invoke. The control plane is always running and always correcting.

This is the same pattern Kubernetes uses for Pods and Deployments, extended to cloud infrastructure. If you already think in terms of desired state and controllers reconciling toward it, Crossplane will feel natural. If you have never operated Kubernetes, you are signing up to run and maintain a Kubernetes cluster before you provision your first bucket.

Feature Comparison

CrossplaneTerraform
Execution modelAlways-on controllers inside KubernetesCLI invoked on demand
StateActual cloud state, tracked as Kubernetes resource statusExplicit state file (local or remote backend)
Drift handlingContinuous reconciliation, auto-correctsDetected only on plan, corrected on apply
Config languageYAML (Kubernetes manifests, Compositions)HCL
Runtime dependencyRequires a running Kubernetes clusterNone; runs anywhere
Abstraction mechanismCompositions + Composite Resource DefinitionsModules
Self-service modelApp teams create Claims via the Kubernetes APITeams run Terraform or use a wrapper (Atlantis, TFC, Spacelift)
Ecosystem maturityGrowing; strong in Kubernetes-native shopsVery large; the de facto IaC standard
Primary audiencePlatform teams building internal platformsAnyone provisioning cloud infrastructure

Provisioning a Postgres Database in Each

Take a common case: a managed Postgres database on AWS.

Terraform

resource "aws_db_instance" "orders" { identifier = "orders" engine = "postgres" engine_version = "16" instance_class = "db.t3.medium" allocated_storage = 20 db_name = "orders" username = "app" password = var.db_password skip_final_snapshot = true vpc_security_group_ids = [aws_security_group.db.id] }

You run terraform apply, the instance is created, and the CLI exits. Terraform records the resource in its state file. To change the instance class later, you edit the HCL, run plan, then apply. To detect that someone resized it manually, you have to run plan and read the diff.

Crossplane

With Crossplane you first install a provider (here the AWS RDS provider), then declare the database as a Kubernetes resource:

apiVersion: rds.aws.upbound.io/v1beta1 kind: Instance metadata: name: orders spec: forProvider: region: us-east-1 engine: postgres engineVersion: "16" instanceClass: db.t3.medium allocatedStorage: 20 dbName: orders username: app passwordSecretRef: namespace: crossplane-system name: orders-db-password key: password skipFinalSnapshot: true writeConnectionSecretToRef: namespace: crossplane-system name: orders-conn

You kubectl apply this manifest. From that point the Crossplane RDS controller owns the instance. If the real instance drifts from spec.forProvider, the controller reconciles it back. The connection details land in a Kubernetes Secret (orders-conn) that your app can mount directly, which is a nice property when your workloads already run in the same cluster.

At this raw level the two are close in verbosity. The divergence shows up when you build abstractions.

Modules vs Compositions

Terraform's abstraction unit is the module. You wrap resources in a module, expose input variables, and consumers call it.

module "postgres" { source = "./modules/postgres" name = "orders" instance_size = "medium" }

The consumer still runs Terraform. They need the Terraform toolchain, credentials, and a way to run apply safely (which in practice means Atlantis, Terraform Cloud, Spacelift, or a CI pipeline).

Crossplane's abstraction unit is a Composition paired with a Composite Resource Definition (XRD). The XRD defines a new Kubernetes API, and the Composition defines how a claim of that API maps to real cloud resources. A platform team authors this once:

apiVersion: apiextensions.crossplane.io/v1 kind: CompositeResourceDefinition metadata: name: xpostgresinstances.platform.acme.io spec: group: platform.acme.io names: kind: XPostgresInstance plural: xpostgresinstances claimNames: kind: PostgresInstance plural: postgresinstances versions: - name: v1alpha1 served: true referenceable: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: size: type: string enum: ["small", "medium", "large"]

Then an application team, without touching AWS APIs or knowing what an RDS instance is, requests a database through a Claim:

apiVersion: platform.acme.io/v1alpha1 kind: PostgresInstance metadata: name: orders spec: size: medium writeConnectionSecretToRef: name: orders-conn

This is the case where Crossplane earns its complexity. The app team interacts with a Kubernetes API your platform team designed. They cannot see or misuse the underlying cloud primitives. Everything they request flows through the same admission controllers, RBAC, and GitOps pipeline as the rest of your Kubernetes resources. If you are building an internal developer platform and you already run Kubernetes, this self-service story is hard to replicate cleanly in Terraform without building a lot of wrapper tooling yourself.

The cost is that authoring Compositions is a real skill. Patch-and-transform mappings, EnvironmentConfigs, and the newer composition functions have a learning curve that goes well beyond writing HCL. Teams routinely underestimate it.

One version note for 2026: Crossplane v2 (August 2025) made Composite Resources namespaced and deprecated the separate Claim type in favor of requesting XRs directly. The Claim-based flow shown above still works for backward compatibility, and the self-service tradeoff against Terraform is the same either way, but new Crossplane setups increasingly skip the Claim and use a namespaced XR.

Drift Handling

This is where the architectures produce different outcomes rather than just different syntax.

Terraform sees drift only when you ask. Run terraform plan and it refreshes state against the real cloud and shows you what changed. Between runs, drift accumulates silently. Teams paper over this with scheduled plan runs in CI and drift-detection products, but the tool itself is passive by design. See managing Terraform drift for the common mitigations.

Crossplane's controllers re-reconcile on an interval, so drift gets corrected automatically and continuously. A manually deleted resource comes back. A hand-edited setting reverts to the declared value. For most platform teams this is the point, though it has a sharp edge: emergency manual fixes get reverted by the control plane unless you pause reconciliation first. The behavior you want during an incident is sometimes the opposite of continuous reconciliation.

When to Choose Crossplane

Crossplane is the stronger choice when:

  • You already operate Kubernetes and have the expertise to run another set of controllers in it responsibly.
  • You are building an internal platform where application teams should request infrastructure through a governed, self-service API rather than opening tickets or learning cloud consoles.
  • You want continuous reconciliation as a feature, and you want out-of-band changes reverted automatically.
  • Your workloads already live in the cluster and consuming connection details as Kubernetes Secrets is natural.
  • You want everything (apps and infrastructure) under one GitOps pipeline driven by Argo CD or Flux.

When to Choose Terraform

Terraform is the stronger choice when:

  • You do not run Kubernetes, or you are unwilling to stand up and operate a cluster just to provision infrastructure.
  • You want the largest ecosystem. Terraform's provider and module coverage is broader than Crossplane's, and finding engineers who know HCL is easy.
  • Your provisioning is occasional and imperative-feeling. Set up an environment, change it a few times, tear it down. An always-on control plane is overhead you do not need.
  • You want a clear, inspectable plan before every change. terraform plan as a review gate is a workflow many teams rely on.
  • You are managing infrastructure that has nothing to do with your application runtime, like organization-wide networking, DNS, or IAM baselines.

Note that these are not mutually exclusive. A common pattern is running Crossplane's Terraform provider so that Crossplane reconciles resources defined in HCL, giving you the control-plane model on top of your existing Terraform modules. It also adds two tools' worth of failure modes, so weigh it carefully.

How Encore Compares

Both tools separate infrastructure configuration from application code. Crossplane keeps it in Kubernetes manifests; Terraform keeps it in HCL. Either way, the service that needs a database and the declaration that provisions the database live in different files, often different repositories, maintained by different people.

For teams whose "infrastructure" is mostly application-level concerns (a Postgres database, a Pub/Sub topic, a cron job, an object storage bucket), a third model changes where the declaration lives. Encore is an open-source backend framework where you declare those resources directly in your TypeScript or Go code, and Encore Cloud provisions the real services in your own AWS or GCP account. There is no YAML control plane to operate and no HCL state file to store.

import { SQLDatabase } from "encore.dev/storage/sqldb"; // Provisions managed Postgres (RDS on AWS, Cloud SQL on GCP, Docker locally). const db = new SQLDatabase("orders", { migrations: "./migrations" });

The relevant contrast with Crossplane is operational. Crossplane gives you continuous provisioning, but you pay for it by running and maintaining a Kubernetes control plane yourself. Encore gives you provisioning derived from your application code into your own cloud account without operating a control plane at all. The open-source tooling parses the code to determine what to provision. The tradeoff is scope: Encore targets application-layer infrastructure, so if your job is fleet-wide networking, IAM at scale, or infrastructure with no application attached, a general provisioning tool remains the right call. Encore is open source with roughly 12,000 GitHub stars and is used in production by teams including Groupon.

One consequence of declaring infrastructure in application code shows up when an AI agent writes or maintains that code. With Crossplane or Terraform, the resource lives in YAML or HCL that sits apart from the service using it, so an agent has to correlate two files to reason about what a change touches. With Encore, the database, topic, or cron job is a typed value in the same file as the code that uses it. Encore also ships an MCP server and editor rules (encore llm-rules init) that give agents schema, trace, and architecture context, and its conventions push agent output toward a consistent, deployable shape. If AI-assisted development is part of how your backend gets built, see TypeScript for AI development.

If your workload fits Encore's primitives, the TypeScript quick start walks through provisioning a backend, or deploy a working service now:

Deploy with Encore

Want to jump straight to a running app? Clone this starter and deploy it to your own cloud.

Deploy

Summary

Crossplane and Terraform both provision cloud resources, but they are built for different jobs. Crossplane is a Kubernetes-native control plane for platform teams who want continuous reconciliation and self-service infrastructure APIs, and who already have the Kubernetes muscle to run it. Terraform is the general-purpose provisioning CLI with the largest ecosystem, no runtime dependency, and a plan-then-apply workflow that stays out of your way between runs. If you have to ask whether you have the Kubernetes maturity for Crossplane, the answer is usually Terraform.

Related Resources

  • Crossplane alternatives
  • Terraform alternatives
  • Infrastructure as code, explained
  • Terraform drift and how to handle it
  • Encore vs Terraform, CDK, Pulumi, and SST

Frequently asked questions

What is the main difference between Crossplane and Terraform?

Crossplane runs as a set of controllers inside Kubernetes and continuously reconciles your cloud infrastructure toward a desired state expressed as Kubernetes resources. Terraform is a CLI that computes a diff against a state file and applies it once when you run it. Crossplane is always-on; Terraform runs when invoked.

Do I need Kubernetes to use Crossplane?

Yes. Crossplane is a set of Kubernetes controllers and Custom Resource Definitions, so it requires a running Kubernetes cluster to host the control plane, even if the infrastructure it manages is not Kubernetes-related. Terraform has no such dependency and runs from any machine or CI runner.

Is Crossplane a drop-in replacement for Terraform?

No. They overlap on provisioning cloud resources, but Crossplane targets platform teams building self-service abstractions on Kubernetes, while Terraform is a general-purpose provisioning CLI. Many teams run both, sometimes wrapping Terraform inside Crossplane via the Terraform provider.

Does Crossplane handle drift better than Terraform?

Crossplane detects and corrects drift continuously because its controllers re-check actual state against desired state on a loop. Terraform only detects drift when you run terraform plan, so out-of-band changes can sit unnoticed until the next apply.

When should I use Crossplane instead of Terraform?

Choose Crossplane when you already operate Kubernetes, want a self-service platform where app teams request infrastructure through Kubernetes APIs, and want continuous reconciliation. Choose Terraform for straightforward provisioning without running a control plane.

Contents
The Core Architectural Difference
Feature Comparison
Provisioning a Postgres Database in Each
Terraform
Crossplane
Modules vs Compositions
Drift Handling
When to Choose Crossplane
When to Choose Terraform
How Encore Compares
Summary
Related Resources
Encore

The backend platform for humans and agents

Build backends in Go or TypeScript and run them on your own AWS or GCP, with the guardrails your team and AI agents need to ship safely.

Ready to build your next backend?

Encore is the Open Source framework for building robust type-safe distributed systems with declarative infrastructure.