A deep dive into Crossplane, the CNCF-graduated Kubernetes-native infrastructure tool. Compare Crossplane vs Terraform vs Pulumi, learn when to use each, and see production patterns for platform engineering.
The Infrastructure Control Plane Revolution
In October 2025, Crossplane achieved CNCF Graduation — joining the ranks of Kubernetes, Prometheus, and Helm as a production-ready, battle-tested project. With the release of Crossplane v2.0 in August 2025, the project has fundamentally changed how platform teams think about infrastructure management.
The core premise is simple but transformative: What if your Kubernetes cluster could manage not just pods, but your entire cloud infrastructure?
What is Crossplane?
Crossplane is a control plane framework that extends Kubernetes to manage any infrastructure resource. Instead of treating infrastructure as code that runs separately, Crossplane treats infrastructure as Kubernetes resources — subject to the same reconciliation, RBAC, and GitOps patterns you use for applications.
The Kubernetes Control Loop for Infrastructure
The magic lies in the reconciliation loop. When you apply a Crossplane resource, Kubernetes continuously ensures the actual cloud state matches your desired state:
1# This is a Kubernetes manifest, not a Terraform file2apiVersion: s3.aws.upbound.io/v1beta13kind: Bucket4metadata:5 name: my-application-data6 namespace: team-backend7spec:8 forProvider:9 region: ap-southeast-210 versioning:11 enabled: true12 serverSideEncryptionConfiguration:13 - rule:14 applyServerSideEncryptionByDefault:15 sseAlgorithm: AES256Apply this with kubectl apply, and Crossplane:
- Creates the S3 bucket in AWS
- Continuously monitors for drift
- Automatically corrects any manual changes
- Reports status back to Kubernetes
No state files. No locking. No separate CLI.
Crossplane v2.0: The 2026 Game Changer
The August 2025 release introduced transformative capabilities:
1. Compose ANY Kubernetes Resource
Previously, compositions could only include Crossplane-managed resources. Now you can combine infrastructure AND native Kubernetes resources:
1# One composite resource provisions everything2apiVersion: platform.buun.io/v1alpha13kind: Application4metadata:5 name: customer-api6 namespace: production7spec:8 database:9 engine: postgresql10 size: large11 replicas: 312 monitoring: enabledThis single resource creates:
- RDS PostgreSQL database
- Kubernetes Deployment
- Service and Ingress
- ConfigMap with connection strings
- Grafana dashboard
- PagerDuty alert rules
2. Namespaced Resources by Default
All managed resources are now namespaced, enabling:
- True multi-tenancy
- Team-scoped infrastructure
- Simplified RBAC
1# Team-scoped database - only team-backend can see/modify2apiVersion: rds.aws.upbound.io/v1beta13kind: Instance4metadata:5 name: orders-db6 namespace: team-backend # Scoped to team7spec:8 forProvider:9 instanceClass: db.t3.medium10 engine: postgres3. Composition Functions (Pipeline Mode)
Move beyond static YAML with real programming:
1apiVersion: apiextensions.crossplane.io/v12kind: Composition3metadata:4 name: application-with-logic5spec:6 compositeTypeRef:7 apiVersion: platform.buun.io/v1alpha18 kind: Application9 mode: Pipeline10 pipeline:11 - step: compute-resources12 functionRef:13 name: function-go-templating14 input:15 apiVersion: gotemplating.fn.crossplane.io/v1beta116 kind: GoTemplate17 source: Inline18 inline:19 template: |20 {{- $replicas := .observed.composite.resource.spec.replicas }}21 {{- $size := .observed.composite.resource.spec.database.size }}22 {{- $cpu := "500m" }}23 {{- $memory := "1Gi" }}24 {{- if eq $size "large" }}25 {{- $cpu = "2000m" }}26 {{- $memory = "4Gi" }}27 {{- end }}28 # Dynamic resource computation based on inputsCrossplane vs Terraform vs Pulumi: The Real Comparison
This is the question every platform team asks. Here's an honest breakdown:
Quick Comparison Table
| Feature | Crossplane | Terraform | Pulumi |
|---|---|---|---|
| Paradigm | Kubernetes control plane | Plan/Apply workflow | Plan/Apply workflow |
| Language | YAML (K8s manifests) | HCL | TypeScript, Python, Go |
| State | Kubernetes etcd (no file) | External state file | Pulumi Cloud or backends |
| Drift Detection | Continuous, automatic | Manual on plan/apply | Manual on preview/up |
| GitOps | Native (Argo CD, Flux) | Requires wrappers | Requires CI/CD |
| Dependencies | Requires Kubernetes | Standalone | Standalone |
| License | Apache 2.0 | BSL (source-available) | Apache 2.0 |
| CNCF Status | Graduated | N/A | N/A |
State Management: The Fundamental Difference
This is where Crossplane fundamentally differs:
Terraform State:
1# You must configure and manage state2terraform {3 backend "s3" {4 bucket = "my-terraform-state"5 key = "prod/terraform.tfstate"6 region = "ap-southeast-2"7 encrypt = true8 dynamodb_table = "terraform-locks" # Locking required!9 }10}Crossplane State:
1# State is just Kubernetes resources - no configuration needed2# Kubernetes etcd handles everything:3# - Storage4# - Locking (via resource versions)5# - Backup (your cluster backup strategy)6# - High availabilityGitOps: Where Crossplane Shines
Crossplane is a first-class GitOps citizen. Your infrastructure is just Kubernetes manifests:
1# argocd-application.yaml2apiVersion: argoproj.io/v1alpha13kind: Application4metadata:5 name: aws-infrastructure6 namespace: argocd7spec:8 project: default9 source:10 repoURL: https://github.com/buun-group/infrastructure11 path: crossplane/production12 targetRevision: main13 destination:14 server: https://kubernetes.default.svc15 syncPolicy:16 automated:17 prune: true18 selfHeal: trueArgo CD syncs your infrastructure the same way it syncs your applications. No special plugins. No wrapper tools.
When to Use Each Tool
Choose Crossplane When:
- Already running Kubernetes in production
- Building an internal developer platform
- Want GitOps for applications AND infrastructure
- Need continuous drift detection and remediation
- Multi-tenant infrastructure requirements
- Fine-grained RBAC inherited from Kubernetes
Choose Terraform When:
- No existing Kubernetes infrastructure
- Team has deep HCL expertise
- Need the widest provider ecosystem
- Simple infrastructure without platform needs
- Organization mandates Terraform
Choose Pulumi When:
- Developers want TypeScript/Python/Go for infra
- Need complex conditional logic
- Want to test infrastructure like application code
- Building reusable infrastructure libraries
Production Patterns: Platform Engineering with Crossplane
Pattern 1: Self-Service Developer Platform
Developers request infrastructure through simple APIs:
1# Developer applies this - no cloud console access needed2apiVersion: database.platform.io/v1alpha13kind: PostgreSQL4metadata:5 name: orders-db6 namespace: team-payments7spec:8 size: medium # Abstracts away instance types9 version: "15"10 backup: daily11 # Platform team's composition handles:12 # - Actual RDS configuration13 # - Security groups14 # - Parameter groups15 # - Monitoring16 # - Backup schedulesPattern 2: Compliance-by-Default
Compositions enforce organizational standards:
1# XRD defines what developers can configure2apiVersion: apiextensions.crossplane.io/v13kind: CompositeResourceDefinition4metadata:5 name: xdatabases.platform.buun.io6spec:7 group: platform.buun.io8 names:9 kind: XDatabase10 plural: xdatabases11 versions:12 - name: v1alpha113 schema:14 openAPIV3Schema:15 type: object16 properties:17 spec:18 type: object19 required: ["size", "engine"]20 properties:21 size:22 type: string23 enum: ["small", "medium", "large"] # Limited options24 engine:25 type: string26 enum: ["postgresql", "mysql"] # Approved engines only27 # Note: No direct instance type selection28 # No VPC configuration29 # No security group access30 # Platform team controls these in CompositionPattern 3: Multi-Cloud Abstraction
Same API, different cloud implementations:
1# Developer API is cloud-agnostic2apiVersion: storage.platform.io/v1alpha13kind: ObjectStorage4metadata:5 name: app-assets6spec:7 region: australia8 versioning: true910# Platform team maintains multiple compositions:11# - composition-objectstorage-aws.yaml → S312# - composition-objectstorage-azure.yaml → Blob Storage13# - composition-objectstorage-gcp.yaml → Cloud StorageReal-World Adoption
Crossplane is running in production at:
- Nike - Internal developer platform
- Autodesk - Multi-cloud infrastructure
- Grafana - Self-service cloud resources
- NASA Science Cloud - Research infrastructure
- SAP - Enterprise platform engineering
- IBM - Hybrid cloud management
- BMW - Connected vehicle infrastructure
Why Enterprises Choose Crossplane
| Challenge | Terraform Solution | Crossplane Solution |
|---|---|---|
| State corruption | Hope + backups | No state files |
| Drift accumulation | Scheduled plans | Continuous reconciliation |
| Developer self-service | Atlantis + approval gates | Native Kubernetes RBAC |
| Multi-tenancy | Workspaces (limited) | Namespaces (native) |
| GitOps | Wrapper tools | First-class support |
The Ecosystem in 2026
Providers
Official Upbound Family Providers:
provider-family-aws- 200+ AWS servicesprovider-family-azure- 180+ Azure servicesprovider-family-gcp- 150+ GCP services
Popular Community Providers:
- Kubernetes (manage other clusters)
- Helm (deploy Helm charts)
- GitHub/GitLab (repos, teams, permissions)
- Datadog/Dynatrace (observability)
- Vault (secrets management)
Installation
1# Install Crossplane2helm repo add crossplane-stable https://charts.crossplane.io/stable3helm install crossplane crossplane-stable/crossplane \4 --namespace crossplane-system \5 --create-namespace67# Install AWS Provider8cat <<EOF | kubectl apply -f -9apiVersion: pkg.crossplane.io/v110kind: Provider11metadata:12 name: provider-aws-s313spec:14 package: xpkg.upbound.io/upbound/provider-aws-s3:v1.0.015EOFGetting Started: Your First Crossplane Resource
Step 1: Configure Provider Credentials
1# Use IRSA (IAM Roles for Service Accounts) - no long-lived creds2apiVersion: aws.upbound.io/v1beta13kind: ProviderConfig4metadata:5 name: default6spec:7 credentials:8 source: IRSAStep 2: Create Infrastructure
1# Create an S3 bucket2apiVersion: s3.aws.upbound.io/v1beta13kind: Bucket4metadata:5 name: my-crossplane-bucket6spec:7 forProvider:8 region: ap-southeast-29 tags:10 ManagedBy: Crossplane11 Environment: developmentStep 3: Verify
1# Check resource status2kubectl get bucket my-crossplane-bucket34# NAME READY SYNCED AGE5# my-crossplane-bucket True True 2m67# Describe for details8kubectl describe bucket my-crossplane-bucketMigration Path from Terraform
Crossplane can import existing resources:
1# Import existing AWS resources2crossplane beta import \3 --provider provider-aws-s3 \4 --resource Bucket \5 --name existing-bucket-name67# Generates Crossplane manifests for existing infrastructureFor gradual migration:
- Start with new resources in Crossplane
- Import existing resources incrementally
- Maintain both temporarily
- Sunset Terraform after migration complete
Should You Switch?
Migrate if:
- Terraform state management is painful
- You want true GitOps for infrastructure
- Building an internal developer platform
- Need continuous drift correction
Stay with Terraform if:
- Current workflow works well
- No Kubernetes in your stack
- Team deeply invested in HCL
Brisbane Platform Engineering
At Buun Group, we help Queensland businesses build modern infrastructure platforms:
- Platform Design — architect self-service developer platforms with Crossplane
- Migration Support — transition from Terraform to Crossplane safely
- Composition Development — build compliant, reusable infrastructure abstractions
- GitOps Integration — implement Argo CD/Flux with infrastructure management
- Training — upskill your team on Kubernetes-native infrastructure
We've deployed Crossplane in production for Australian enterprises. We know the patterns that work and the pitfalls to avoid.
Ready to modernize your infrastructure?
Topics
Comments
Sign in to join the conversation
LoginNo comments yet. Be the first to share your thoughts!
Found an issue with this article?
