Back to Blog
CrossplaneKubernetesPlatform EngineeringInfrastructure as CodeXRDCompositionsGitOpsCloud InfrastructureCNCFOpen Source

Crossplane — Kubernetes-Native Infrastructure Provisioning with Composite Resources and XRDs

A practical guide to Crossplane, the open-source CNCF control-plane framework that turns a Kubernetes cluster into a universal control plane for provisioning and continuously reconciling external infrastructure as ordinary Kubernetes objects: the building blocks — Providers that install managed-resource CRDs for a cloud or SaaS system, Managed Resources that wrap one external resource each and reconcile it forever, Compositions that bundle several managed resources into one higher-level unit, CompositeResourceDefinitions (XRDs) that define your own custom platform API and its schema, and Composite Resources that developers create to get infrastructure; installing the core controllers with the crossplane-stable Helm chart into the crossplane-system namespace, installing a Provider package such as provider-aws-s3 by digest for reproducibility and customising its pod with a DeploymentRuntimeConfig, declaring managed resources directly with the Crossplane v2 namespace-first model where both composite and managed resources are namespaced by default and namespaced provider kinds carry the .m group suffix like rds.aws.m.upbound.io, defining a platform API with an apiextensions.crossplane.io/v2 CompositeResourceDefinition with a scope field defaulting to Namespaced plus Cluster and LegacyCluster modes and served/referenceable versions, wiring a Composition of composition functions with mode Pipeline where each step references an installed Function package like function-patch-and-transform and functions can also be written in Go, Python, KCL, CUE, or Helm-style templates and can request existing resources, the v2 breaking changes that removed native patch-and-transform, ControllerConfig, external secret stores, composite-resource connection details, and the default package registry — checkable with crossplane beta upgrade check — plus GitOps with Argo CD or Flux, the alpha Operation type for day-two workflows, and a production checklist.

2026-07-18

Infrastructure as Data, Not Scripts

Most infrastructure-as-code tools run to completion and then walk away. You apply a plan, the resources appear, and nothing watches them afterward. Drift creeps in, someone edits a console setting, and your declared state and real state quietly diverge until the next apply surprises everyone.

Crossplane takes a different stance. It turns your Kubernetes cluster into a universal control plane that provisions and continuously reconciles external infrastructure — databases, buckets, networks — as ordinary Kubernetes objects. The same control loop that keeps a Deployment at three replicas keeps your RDS instance matching its spec.

Crossplane is an open-source CNCF project licensed under Apache 2.0. This guide installs it, wires up a cloud provider, and then builds the part that actually matters for platform teams: a custom API your developers can call without knowing a single cloud SKU.

Note

This article targets Crossplane v2, the latest major line (v2.3 at the time of writing). v2 made a large architectural shift to a namespace-first model and removed several v1 features. If you run a v1.x control plane, read the What’s New in v2 page and the breaking-changes section below before upgrading. Always check flags and API versions against the official docs for your installed version.

The Building Blocks

Crossplane introduces a handful of concepts. They stack cleanly, so it is worth naming them before touching YAML. Each layer builds on the one below it.

  • Providers. Packages that teach Crossplane how to talk to an external API — AWS, GCP, Azure, or SaaS systems. Installing one adds a library of managed-resource CRDs.
  • Managed Resources (MRs). The granular Kubernetes objects a provider installs — an S3 Bucket or an RDSInstance. One MR maps to one external resource, reconciled continuously.
  • Compositions.A template that bundles several managed resources into one higher-level unit — a “database” that is really an instance, a subnet group, and a security group.
  • CompositeResourceDefinitions (XRDs). The schema for your own custom API. An XRD defines the fields a developer fills in; the Composition decides what those fields build.
  • Composite Resources (XRs). Instances of the API you defined — what a developer actually creates to get infrastructure.

The theme is familiar to anyone building an internal platform: hide primitives behind a curated interface. It is the same instinct behind building developer portals with Backstage, only here the abstraction is a live Kubernetes API rather than a catalog entry.

Installing Crossplane

Crossplane ships as a Helm chart and runs inside its own namespace. You need a Kubernetes cluster and Helm v3.2.0 or later. The chart installs the core controllers and the package manager that later pulls in providers and functions.

# Add the stable Crossplane Helm repository
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update

# Install into the crossplane-system namespace
helm install crossplane \
  --namespace crossplane-system \
  --create-namespace crossplane-stable/crossplane

# Confirm the pods are running
kubectl get pods -n crossplane-system

That is the whole footprint: a couple of controller pods in crossplane-system. Everything else — cloud access, higher-level APIs — arrives later as packages you install declaratively, which keeps the whole control plane describable in Git.

Providers — Extending the Control Plane

A Provider is a Crossplane package that installs a controller and a set of managed-resource CRDs for one external system. You install one by applying a Provider object; the package manager pulls the image and registers the new resource kinds.

apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: crossplane-contrib-provider-aws-s3
spec:
  package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0

Providers are large, so the ecosystem splits big clouds into service-scoped packages — you install provider-aws-s3 rather than one monolith for every AWS service. Check readiness with kubectl get providers; the HEALTHY column reports Unknown during install and True once the controller is up.

Note

For deterministic, repeatable installs, pin the package by digest instead of tag — provider-aws-s3@sha256:... — so a moved tag can never change what runs. To customise the provider pod (extra args, replica counts), reference a DeploymentRuntimeConfig via spec.runtimeConfigRef; it is a beta feature enabled by default, and its runtime container is named package-runtime.

Managed Resources — Cloud as Kubernetes Objects

Once a provider is healthy, its CRDs let you declare external infrastructure directly. A managed resource is the thinnest possible wrapper: one Kubernetes object, one cloud resource, reconciled forever. Edit the spec and Crossplane converges the real resource; delete the object and it tears the resource down.

In Crossplane v2, managed resources are namespaced by default, which aligns them with standard Kubernetes multi-tenancy. Namespaced provider kinds are distinguished by an .m group suffix — for example rds.aws.m.upbound.io/v1beta1 — so the same team can own its resources inside its own namespace.

apiVersion: s3.aws.m.upbound.io/v1beta1
kind: Bucket
metadata:
  name: reports-bucket
  namespace: analytics
spec:
  forProvider:
    region: eu-central-1
  providerConfigRef:
    name: default

The providerConfigRef points at a ProviderConfig that holds credentials and settings — how the provider authenticates to the cloud. Managing a raw fleet of these objects by hand is possible but misses the point. The real leverage comes from composing them, which is the next layer.

CompositeResourceDefinitions — Defining Your Platform API

This is where Crossplane stops being “Terraform inside Kubernetes” and becomes a platform tool. An XRD defines a brand-new API — its group, its kind, and the schema of fields a consumer may set. Developers then use that API without seeing a single provider resource underneath.

apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
  name: xdatabases.platform.example.org
spec:
  scope: Namespaced
  group: platform.example.org
  names:
    kind: XDatabase
    plural: xdatabases
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                size:
                  type: string
                region:
                  type: string
              required:
                - size
                - region

A few rules matter. metadata.name must be the plural plus a dot plus the group. Crossplane recommends prefixing the composite kind with X. Each version needs both served: true and referenceable: true to be usable, and only one version may be referenceable at a time.

Note

The v2 XRD API (apiextensions.crossplane.io/v2) adds a scope field that defaults to Namespaced. Use Cluster for cluster-scoped composites, or LegacyCluster for the v1-compatible mode that still supports claims. If you use the older v1 XRD API, scope defaults to LegacyCluster automatically.

Compositions and Composition Functions

The XRD says what a developer can ask for; a Composition says how to build it. In Crossplane v2 a Composition is a pipeline of composition functions— each step is a small program that reads the composite’s spec and returns the desired managed resources.

Functions are themselves packages, installed like providers. The most common is function-patch-and-transform, which templates resources with field patches and value transforms without writing code.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-patch-and-transform
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2

The Composition wires that function into a pipeline. Its compositeTypeRef points at the API defined by your XRD, mode: Pipeline selects the function-based engine, and each step names a functionRef plus its input.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: xdatabases.platform.example.org
spec:
  compositeTypeRef:
    apiVersion: platform.example.org/v1alpha1
    kind: XDatabase
  mode: Pipeline
  pipeline:
    - step: render-resources
      functionRef:
        name: function-patch-and-transform
      input:
        apiVersion: pt.fn.crossplane.io/v1beta1
        kind: Resources
        # resources and patches omitted for brevity

Note that the Composition object itself still uses apiextensions.crossplane.io/v1, while the XRD uses v2 — two different resources, two different API versions. The compositeTypeRef uses your own API group, not a Crossplane one.

Note

You are not limited to patch-and-transform. Crossplane maintains functions that template resources with Go/Helm-style templates, CUE, KCL, or Python, and you can write your own in Go or Python. Because a pipeline can chain functions, a real Composition often validates input in one step, renders resources in another, and derives outputs in a third. A v2 enhancement even lets a function request existing cluster or namespaced resources it needs, and Crossplane re-invokes it with those resources attached.

Consuming the API

With the XRD and Composition in place, a developer creates a composite resource — an instance of the API you designed. They set the fields your schema exposed and nothing else. Crossplane runs the pipeline and provisions every underlying managed resource, reconciling them all continuously.

apiVersion: platform.example.org/v1alpha1
kind: XDatabase
metadata:
  name: orders-db
  namespace: payments
spec:
  size: large
  region: eu-central-1

That short manifest is the entire developer-facing surface. No IAM policy, no subnet group, no engine version — those live in the Composition, owned by the platform team. This is exactly the “golden path” idea from platform engineering and internal developer platforms: a paved, opinionated route that is easier to take than to avoid.

What Changed in Crossplane v2

v2 is a meaningful break from v1, and skipping the details will bite you on an upgrade. The headline is the namespace-first model: both composite and managed resources are namespaced by default, and the old duplication between a cluster-scoped composite and its namespaced claim is gone — you create the resource directly in a namespace.

v2 also lets a Composition include any Kubernetes resource, not only Crossplane managed ones — a Deployment or a third-party custom resource can sit in the same composition as an RDSInstance. That is what lets a single manifest deploy an app and the infrastructure it needs together.

The removals are the sharp edges. v2 drops native patch-and-transform composition (you now use the function), the ControllerConfig type, external secret stores, composite-resource connection details, and the default registry for packages. Before upgrading, run crossplane beta upgrade check with the v1.20 CLI against your v1.x control plane to scan for exactly these features.

Note

Cluster-scoped managed resources still work in v2 but are now a legacy feature that Crossplane intends to deprecate and remove later. Provider support for namespaced resources varies by maintainer — AWS resources were fully supported at launch, with others catching up — so verify your specific provider before committing to the namespaced model.

Crossplane and GitOps

Because every Crossplane object is a Kubernetes manifest, the whole control plane fits a GitOps workflow with no special tooling. Argo CD or Flux syncs your providers, functions, XRDs, compositions, and composite resources from Git, and Crossplane reconciles the cloud to match. Infrastructure changes become pull requests.

This is a genuine advantage over an imperative apply. Continuous reconciliation means out-of-band console edits are corrected automatically, and the desired state is always the file in your repository — the same discipline we apply to Terraform at scale, but enforced by a live control loop rather than a scheduled plan.

Beyond Provisioning — Operations

v2 introduces an Operation type for declarative operational workflows — annotating resources with operational data, triggering maintenance tasks, or encoding custom policies. It is an alpha feature, so treat it as experimental and keep it out of critical paths until it stabilises.

The direction is clear, though: Crossplane wants to be the control plane for both provisioning and day-two operations. Paired with strong cost discipline — the kind we cover in Kubernetes cost optimization — a single reconciling API for your whole platform is a compelling place to standardise.

Production Checklist

  • Pin packages by digest. Reference providers and functions by @sha256 digest, not a floating tag, so installs are reproducible.
  • Design the XRD as a real API. Expose only the fields a consumer should set, mark the rest required, and version deliberately — an XRD is a contract.
  • Keep credentials in ProviderConfig. Never inline secrets in managed resources; reference a ProviderConfig and manage its secret separately.
  • Scan before upgrading to v2. Run crossplane beta upgrade check to catch removed features — native patch-and-transform, ControllerConfig, external secret stores.
  • Verify provider namespaced support. The .m namespaced model depends on the provider; confirm yours supports it before adopting namespace-first.
  • Manage everything in Git. Sync providers, functions, XRDs, and compositions with Argo CD or Flux so the control plane is fully declarative.

Crossplane’s bet is that infrastructure belongs behind an API that never stops reconciling, and that platform teams should be able to publish that API themselves rather than hand developers raw cloud primitives. If your organisation already lives in Kubernetes and you are tired of drift and copy-pasted modules, it is worth a proof of concept.

Crossplane rarely stands alone. When Crossplane-provisioned clusters need to scale their own compute, pair it with Karpenter autoscaling so node pools grow and shrink with demand. For teams weighing whether to build platform abstractions in Crossplane or a general-purpose IaC tool, our guide to Terraform advanced patterns covers the trade-offs of modules and remote state, while Backstage developer platforms shows how a self-service portal can front the composite APIs you publish here.

Your infrastructure-as-code tool applies a plan and walks away so console edits silently drift your real state from your declared state, your developers copy-paste cloud modules they do not understand to stand up a database, or you want a self-service platform API but do not want to hand your teams raw IAM policies and subnet groups?

We design and implement Crossplane control planes — from installing the core controllers and the right service-scoped Provider packages pinned by digest, through ProviderConfig credential management kept out of resource specs, designing CompositeResourceDefinitions as real versioned platform APIs that expose only the fields a consumer should set, building Compositions as pipelines of composition functions in patch-and-transform, KCL, or Python so one manifest provisions a database, its security groups, and its networking together, adopting the Crossplane v2 namespace-first model where it fits and verifying each provider's namespaced support first, wiring the whole control plane into a GitOps workflow with Argo CD or Flux so every infrastructure change is a reviewed pull request that a live control loop reconciles and drift is corrected automatically, and running crossplane beta upgrade check before any v1-to-v2 migration so removed features surface before they break you. Let's talk.

Let's Talk

DataSOps Consulting

Need help implementing this in production?

We build and operate data pipelines, AI systems, and observability stacks for engineering teams. Reach out for a free 30-minute architecture review.