All Articles

Pulumi vs Crossplane: Which Infrastructure Tool to Use in 2026?

Pulumi vs Crossplane comparison — architecture, use cases, team fit, and when to use each for managing cloud infrastructure in 2026.

DevOpsBoysApr 21, 20265 min read
Share:Tweet

Both Pulumi and Crossplane manage cloud infrastructure. But they come from completely different philosophies — and choosing the wrong one for your context creates long-term pain.

Pulumi is an IaC tool. Crossplane is a Kubernetes-native control plane. They overlap in what they can provision but are built for different workflows and teams.


What Each Tool Is

Pulumi

Pulumi lets you write infrastructure code using real programming languages — Python, TypeScript, Go, Java, C#. No YAML, no HCL.

typescript
// TypeScript example
import * as aws from "@pulumi/aws";
 
const bucket = new aws.s3.Bucket("my-bucket", {
    acl: "private",
    versioning: { enabled: true },
    tags: { Environment: "production" }
});
 
const distribution = new aws.cloudfront.Distribution("cdn", {
    origins: [{
        domainName: bucket.bucketRegionalDomainName,
        originId: bucket.id,
    }],
    enabled: true,
    // ...
});
 
export const bucketName = bucket.id;
export const cdnUrl = distribution.domainName;

Pulumi feels like software development. You get loops, conditionals, functions, and the full power of your chosen language to model infrastructure.

Crossplane

Crossplane is a Kubernetes operator that manages cloud resources using the Kubernetes API — YAML manifests, CRDs, controllers, and reconciliation loops.

yaml
# Crossplane example — provision an RDS database
apiVersion: database.aws.upbound.io/v1beta1
kind: RDSInstance
metadata:
  name: my-database
spec:
  forProvider:
    region: ap-south-1
    instanceClass: db.t3.medium
    engine: postgres
    engineVersion: "15"
    allocatedStorage: 20
    dbName: myapp
    username: admin
    passwordSecretRef:
      name: db-password
      namespace: default
      key: password
  writeConnectionSecretToRef:
    namespace: default
    name: my-database-conn

Crossplane runs inside your Kubernetes cluster. It watches for these CRDs and provisions/reconciles the actual cloud resources continuously.


Architecture Comparison

Pulumi Architecture

Developer writes code (TypeScript/Python/Go)
→ pulumi up
→ Pulumi engine diffs desired vs current state
→ Calls cloud APIs to create/update/delete
→ Stores state in Pulumi Cloud or S3/Azure Blob/GCS

Pulumi is a CLI tool like Terraform. You run it manually or in CI/CD. It's imperative in feel (you call pulumi up) even though the underlying model is declarative.

Crossplane Architecture

Kubernetes cluster runs Crossplane operator
→ You apply YAML manifests (kubectl apply)
→ Crossplane controller reconciles continuously
→ Creates/updates cloud resources to match desired state
→ State lives in Kubernetes etcd (the cluster IS the state store)

Crossplane is always running, always reconciling. If someone manually changes a cloud resource, Crossplane will detect the drift and correct it on the next reconciliation cycle.


Feature Comparison

FeaturePulumiCrossplane
LanguagePython, TS, Go, Java, C#YAML / Kubernetes CRDs
State storagePulumi Cloud, S3, GCSKubernetes etcd
ReconciliationOn pulumi upContinuous (every ~30s)
Drift detectionYes (on run)Yes (continuous)
Cloud coverage120+ providers30+ providers (provider packages)
Custom abstractionsFunctions, classesCompositeResourceDefinitions (XRDs)
Multi-cloudYesYes
TestingFull unit test supportLimited
Kubernetes resourcesYes (kubernetes provider)Yes (native)
Learning curveLow for devs, medium for opsHigh (K8s + Crossplane concepts)
Self-service portalsManualYes (built-in via K8s RBAC)

Abstractions and Reuse

This is where both tools differentiate themselves from Terraform HCL.

Pulumi — ComponentResources

typescript
// Build a reusable VPC + EKS cluster component
class EksCluster extends pulumi.ComponentResource {
    public readonly kubeconfig: pulumi.Output<string>;
 
    constructor(name: string, args: EksArgs, opts?: pulumi.ResourceOptions) {
        super("myorg:index:EksCluster", name, {}, opts);
 
        const vpc = new awsx.ec2.Vpc(`${name}-vpc`, {
            numberOfAvailabilityZones: 2,
        }, { parent: this });
 
        const cluster = new eks.Cluster(`${name}-cluster`, {
            vpcId: vpc.vpcId,
            subnetIds: vpc.privateSubnetIds,
            instanceType: args.instanceType ?? "t3.medium",
            desiredCapacity: args.nodeCount ?? 2,
        }, { parent: this });
 
        this.kubeconfig = cluster.kubeconfig;
    }
}
 
// Use it:
const prod = new EksCluster("production", { nodeCount: 5 });
const staging = new EksCluster("staging", { nodeCount: 2 });

Crossplane — Composite Resource Definitions (XRDs)

yaml
# Define a composite resource (abstract)
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xclusters.platform.example.com
spec:
  group: platform.example.com
  names:
    kind: XCluster
    plural: xclusters
  claimNames:
    kind: Cluster
    plural: clusters
  versions:
  - name: v1alpha1
    served: true
    referenceable: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              region:
                type: string
              nodeCount:
                type: integer
---
# Composition: how XCluster maps to real resources
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: cluster-aws
spec:
  compositeTypeRef:
    apiVersion: platform.example.com/v1alpha1
    kind: XCluster
  resources:
  - name: eks-cluster
    base:
      apiVersion: eks.aws.upbound.io/v1beta1
      kind: Cluster
      # ... EKS config
  - name: node-group
    base:
      apiVersion: eks.aws.upbound.io/v1beta1
      kind: NodeGroup
      # ... node group config

Crossplane compositions are more complex but enable self-service — developers can kubectl apply a Cluster claim and get a full EKS cluster without knowing AWS internals.


Self-Service Infrastructure

This is Crossplane's killer feature. With XRDs:

yaml
# Developer applies this (they don't know about VPCs, node groups, etc.)
apiVersion: platform.example.com/v1alpha1
kind: Cluster
metadata:
  name: my-team-cluster
  namespace: team-alpha
spec:
  region: ap-south-1
  nodeCount: 3

Kubernetes RBAC controls who can create what:

yaml
# team-alpha can create Clusters but not modify production infra
kind: Role
rules:
- apiGroups: ["platform.example.com"]
  resources: ["clusters"]
  verbs: ["get", "list", "create", "delete"]

This pattern — platform as a product — is where Crossplane shines. Pulumi requires a wrapper service (Terraform Cloud, custom API) to achieve the same self-service experience.


When to Use Pulumi

  • Your team writes code (Python/TypeScript) and finds YAML/HCL painful
  • You need complex conditional logic, loops, and abstractions in infrastructure
  • You want full unit test coverage for infrastructure
  • You're migrating from Terraform and want more power
  • You don't want to run Kubernetes just to manage infrastructure

Not a fit if: Your team is ops-heavy (not developers), or you're building a self-service platform.


When to Use Crossplane

  • You're building an internal developer platform (self-service infrastructure for dev teams)
  • Your infrastructure should be GitOps-native (kubectl apply, ArgoCD, FluxCD)
  • You need continuous drift correction, not just on-demand runs
  • You already run Kubernetes and want infra to live in the same system
  • You want RBAC over who can provision what infrastructure

Not a fit if: You don't run Kubernetes, or your team doesn't understand K8s controllers and CRDs.


Can You Use Both?

Yes — and some large companies do. Pulumi handles the foundational infrastructure (VPCs, clusters, shared services), while Crossplane runs inside the cluster to provision application-level resources for teams.

But for most teams: pick one. Running both adds significant operational complexity.


Summary

PulumiCrossplane
ParadigmIaC with real languagesKubernetes-native control plane
Best forDeveloper-friendly IaCPlatform engineering + self-service
StateExternal backendKubernetes etcd
ReconciliationOn-demandContinuous
Self-serviceRequires wrapperBuilt-in via K8s RBAC
Learning curveLow-mediumHigh
VerdictTerraform alternative for devsPlatform teams building developer platforms

If you're building a developer platform where teams can self-serve infrastructure, Crossplane is the right architecture. If you're a DevOps engineer who finds Terraform's HCL limiting and wants to write real code, Pulumi is the right move.

Experiment with both on DigitalOcean Kubernetes — $200 free credit. Spin up a cluster, install Crossplane, and compare it with a Pulumi TypeScript stack for the same infrastructure.

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments