Key takeaways

  • Crossplane turns your Kubernetes cluster into a control plane that provisions cloud infrastructure through custom resources — no terraform apply, just YAML that a controller reconciles continuously.
  • provider-ovh is an open-source Crossplane provider built by Edixos, generated with Upjet on top of the official OVHcloud Terraform provider. It exposes 135+ OVHcloud resources as CRDs and is published on the Upbound Marketplace.
  • This guide is end to end: a kind cluster, Crossplane, the provider, credential wiring, and a real managed Kubernetes cluster provisioned on OVHcloud.

Introduction

Most teams still provision OVHcloud the imperative way: a Terraform run here, a console click there, a script nobody wants to touch. It works until you need self-service, drift correction, and RBAC — the things a platform team actually has to deliver.

Crossplane flips the model. Your Kubernetes cluster becomes the control plane, and infrastructure becomes a set of custom resources that a controller keeps reconciled. At Edixos we built provider-ovh to bring that model to OVHcloud, and this article walks through it from an empty machine to a running managed Kubernetes cluster on OVH.

If you have used Crossplane with AWS or GCP, this will feel familiar. If you have not, you will have a working control plane in about fifteen minutes.

What is Crossplane?

Crossplane is a CNCF open-source control plane that extends Kubernetes so it can provision and manage external infrastructure. You install a provider, which registers Custom Resource Definitions (CRDs) for a cloud’s resources, and from then on you declare that infrastructure as Kubernetes objects. A controller reconciles each object against the provider’s API, correcting drift and cleaning up on delete.

The payoff is that infrastructure inherits everything Kubernetes already gives you: declarative YAML, RBAC, GitOps, admission control, and a single API surface across clouds. Instead of teams filing tickets, they apply a manifest — and the platform enforces the guardrails.

What is provider-ovh?

provider-ovh is the Crossplane provider for OVHcloud, built and maintained by Edixos. It exposes OVHcloud services — public cloud projects, managed Kubernetes, databases, private networks, IAM, object storage, and more — as Kubernetes managed resources. It is available on the Upbound Marketplace and the source lives on GitHub.

The story behind it is worth telling, because it explains why the project exists at all. When we first looked, there was no official or community Crossplane provider for OVHcloud. We built the first one and offered to donate it — the response at the time was that community providers could not be considered official, and it would not be maintained internally. Facing other commitments, the project went dormant for nearly a year.

Then three engineers — Nicolas, Kevin, and Ferréol from Theseus — got in touch. They were already running the provider in production, powering a Database-as-a-Service offering for the French public sector. Shortly after, Fabien opened a pull request bumping the provider to Crossplane 2.0. That was the catalyst. The provider was reborn as v2.9.0, with Endre Karlson and Alegrowin joining as co-maintainers, and it is now an actively maintained community project covering 135+ OVH resources and 90%+ of the Terraform provider surface, on both Crossplane 1.x and 2.x.

The lesson we keep repeating: real users in production are what keep infrastructure projects alive.

How provider-ovh is built

provider-ovh is generated, not hand-written. It uses Upjet, the Crossplane code-generation toolkit, on top of the official OVHcloud Terraform provider — pinned in this release to version 2.13.1. Upjet reads the Terraform provider’s schema and generates XRM-conformant managed resources: Go API types, deepcopy functions, controllers, and CRD manifests.

That approach is why coverage is so broad. Instead of implementing every OVH endpoint by hand, we regenerate the provider whenever OVHcloud ships a new Terraform version, and the new resources come along for free. The current release ships 281 CRD manifests — the 135+ resources appear in two flavors, cluster-scoped and namespaced, thanks to Crossplane v2 (more on that below). Bumping the underlying Terraform provider is a documented, four-command routine: update the Makefile and go.mod, run make generate, and commit the regenerated schema, types, and CRDs.

Prerequisites

Before you start, install the following tools locally. Everything runs on your machine except the OVHcloud API calls, which need a real account.

  • Docker (or another container runtime kind supports)
  • kind — to run a throwaway Kubernetes cluster
  • kubectl — to talk to the cluster
  • Helm — to install Crossplane
  • An OVHcloud account with API credentials (we generate them in Step 5)

Step 1: Create a local kind cluster

Crossplane is a lightweight control plane, so a single-node kind cluster is plenty for this walkthrough. Create one:

kind create cluster --name crossplane-ovh

Confirm the cluster is up and kubectl points at it:

kubectl cluster-info --context kind-crossplane-ovh

This cluster never runs your OVH workloads — it only hosts the Crossplane control plane that provisions them.

Step 2: Install Crossplane

Install Crossplane with its official Helm chart into a dedicated crossplane-system namespace. This deploys the core Crossplane controllers and the package manager that installs providers.

helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update

helm install crossplane \
  crossplane-stable/crossplane \
  --namespace crossplane-system \
  --create-namespace

Wait for the pods to become ready, then verify the installation:

kubectl get pods -n crossplane-system

You should see the crossplane and crossplane-rbac-manager pods running. Crossplane has also registered its own core CRDs — providers, configurations, providerconfigs — which the next step builds on.

Step 3: Install provider-ovh

Installing a provider is itself a Kubernetes resource: you apply a Provider object pointing at the package image, and Crossplane’s package manager pulls it and registers its CRDs. Apply provider-ovh from the Upbound registry:

cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-ovh
spec:
  package: xpkg.upbound.io/edixos/provider-ovh:v2.10.1
EOF

Pin an explicit version tag in production — check the Marketplace for the latest release rather than using :latest. Watch the provider become healthy:

kubectl get providers

Once the INSTALLED and HEALTHY columns both read True, the provider’s controllers are running and every OVH CRD is registered in your cluster.

Step 4: Discover the managed resources

Because the provider registers its resources as standard CRDs, you explore them with plain kubectl — no special tooling. This is one of the quiet advantages of the Crossplane model: your infrastructure catalog is queryable from the same API as everything else.

List the OVH resource types the provider added:

kubectl get crds | grep ovh.edixos.io

You will see resources grouped by service — kube.ovh.edixos.io, network.ovh.edixos.io, cloud.ovh.edixos.io, databases.ovh.edixos.io, iam.ovh.edixos.io, and more. To inspect the fields of any resource, use kubectl explain:

kubectl explain cluster.kube.ovh.edixos.io --recursive

That prints the full spec for a managed OVHcloud Kubernetes cluster — every forProvider field you can set — straight from the CRD’s OpenAPI schema. The complete API reference is also published at doc.crds.dev.

Step 5: Configure OVHcloud credentials

The provider needs OVHcloud API credentials to reconcile anything real. You supply them through a Kubernetes Secret, then point a ProviderConfig at that Secret. First, generate an application key, application secret, and consumer key from the OVH API console, granting the API rights your resources require.

Create the Secret in the crossplane-system namespace:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: provider-ovh
  namespace: crossplane-system
type: Opaque
stringData:
  credentials: |
    {
      "endpoint": "ovh-eu",
      "application_key": "YOUR_APP_KEY",
      "application_secret": "YOUR_APP_SECRET",
      "consumer_key": "YOUR_CONSUMER_KEY"
    }
EOF

Now create a ProviderConfig named default that tells the provider where to read those credentials:

cat <<EOF | kubectl apply -f -
apiVersion: ovh.edixos.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: Secret
    secretRef:
      name: provider-ovh
      namespace: crossplane-system
      key: credentials
EOF

Never commit real credentials to Git. In production, source the Secret from an external secret store — provider-ovh supports Crossplane’s external-secret-store integration for exactly this.

Step 6: Provision your first OVHcloud resources

With credentials wired up, provisioning is now a kubectl apply. Let’s build a real stack: a private network, a subnet inside it, and a managed Kubernetes cluster attached to both. This is the same flow that I walk through in the Platform Engineering with Crossplane & OVHcloud talk. Each manifest references the default ProviderConfig from the previous step.

Start with a private network. Replace serviceName with your OVH public cloud project ID:

apiVersion: network.ovh.edixos.io/v1alpha1
kind: PrivateNetwork
metadata:
  name: sample-1
  labels:
    managed-by: crossplane
spec:
  providerConfigRef:
    name: default
  forProvider:
    name: sample-1
    serviceName: 980cbcf06e6a4e6e8a91a7d125b26bba
    regions:
      - DE1

Add a subnet inside that network — this is where your cluster’s nodes get their IP addresses. It references the private network by name with networkIdRef, so Crossplane waits for the network and wires the ID in for you:

apiVersion: network.ovh.edixos.io/v1alpha1
kind: Subnet
metadata:
  name: subnet-1
  labels:
    managed-by: crossplane
spec:
  providerConfigRef:
    name: default
  forProvider:
    serviceName: 980cbcf06e6a4e6e8a91a7d125b26bba
    networkIdRef:
      name: sample-1
    region: DE1
    start: 192.168.168.100
    end: 192.168.168.200
    network: 192.168.168.0/24
    dhcp: true
    noGateway: false

Now a managed Kubernetes cluster that references both the network and the subnet by name — Crossplane resolves each reference and injects the resulting IDs automatically:

apiVersion: kube.ovh.edixos.io/v1alpha1
kind: Cluster
metadata:
  name: hello-edixos
spec:
  providerConfigRef:
    name: default
  forProvider:
    name: "hello-edixos"
    region: DE1
    serviceName: 980cbcf06e6a4e6e8a91a7d125b26bba
    privateNetworkIdRef:
      name: sample-1
    nodesSubnetIdRef:
      name: subnet-1

Apply them and watch Crossplane reconcile against the OVHcloud API:

kubectl apply -f privatenetwork.yaml -f subnet.yaml -f cluster.yaml
kubectl get managed

The kubectl get managed command lists every external resource Crossplane is tracking, with READY and SYNCED columns. When both read True, your cluster exists on OVHcloud — provisioned entirely through the Kubernetes API. Add a NodePool the same way, referencing the cluster with kubeIdRef, and you have a complete managed Kubernetes environment described as YAML.

Cluster-scoped vs namespaced resources

provider-ovh ships every resource in two variants because it targets Crossplane 2.x. Cluster-scoped resources use the classic network.ovh.edixos.io group; namespaced resources use the *.m.ovh.edixos.io groups (the m marks the modern, namespaced API). Namespaced resources let you scope OVH infrastructure to a Kubernetes namespace, which is the foundation for safe multi-tenancy — each team gets its own namespace, its own RBAC, and its own slice of infrastructure. That doubling is why the release contains 281 CRD manifests for roughly 135 logical resources.

Beyond raw resources: Compositions

Applying individual managed resources is powerful, but it is not yet a platform. The real leverage comes from Compositions: you define a Composite Resource Definition (an XRD) that presents a simple, opinionated API — say, EdixosCluster — and a Composition that expands it into the private network, subnet, cluster, and node pool underneath. Developers request one high-level resource; the platform assembles the details with your standards baked in.

The provider-ovh repository ships a complete Composition example that does exactly this. This is where Crossplane stops being a Terraform replacement and becomes a self-service platform — the pattern we build for clients at Edixos every week.

Troubleshooting

When a resource will not become ready, the reconcile loop tells you why. Describe the managed resource and read its conditions and events:

kubectl describe cluster.kube.ovh.edixos.io hello-edixos

Most failures fall into three buckets: credential errors (the Secret JSON is malformed, or the OVH token lacks the required API rights), reference errors (a *Ref points at a resource that does not exist or is not yet ready), and quota or region errors returned straight from the OVHcloud API. For controller-level issues, install the provider with the --debug flag via a DeploymentRuntimeConfig and check the provider pod logs in crossplane-system.

Conclusion

You now have the full loop: a kind cluster running Crossplane, provider-ovh installed and healthy, credentials wired through a ProviderConfig, and real OVHcloud infrastructure provisioned as Kubernetes resources. The same manifests drop straight into a Git repository and a GitOps pipeline — which is the whole point.

provider-ovh started as a project OVHcloud declined to adopt and survived because engineers ran it in production and pushed it forward. It is open source, community-maintained, and built for teams who want OVHcloud to behave like every other cloud in a Crossplane control plane. Star it on GitHub, try it on your own project, and open an issue if something is missing — that feedback is exactly what keeps it moving.

Crossplane and provider-ovh are one piece of a larger platform-engineering practice. For the wider context, read why Kubernetes beats Terraform for platform engineering and our field-tested blueprint for building Kubernetes as a Service — both grounded in the decade of production Kubernetes behind this provider.

Let’s talk about your Crossplane platform

Do you want to expose OVHcloud — or any cloud — as self-service infrastructure your teams consume through the Kubernetes API? Let’s discuss your platform goals and see how a custom Crossplane control plane can bring you speed, governance, and scale.

Straight answers

Frequently asked questions

What is provider-ovh?

provider-ovh is a Crossplane provider built by Edixos that exposes OVHcloud resources as Kubernetes custom resources. It is generated with Upjet on top of the official OVHcloud Terraform provider, covers 135+ resources, and is published on the Upbound Marketplace under edixos/provider-ovh for Crossplane 1.x and 2.x.

Do I need OVHcloud API credentials to use provider-ovh?

Yes. The provider authenticates to the OVHcloud API using an application key, application secret, and consumer key. You generate these tokens from the OVH API console, store them in a Kubernetes Secret, and reference that Secret from a ProviderConfig so Crossplane can reconcile your resources.

Can I run Crossplane and provider-ovh on a local kind cluster?

Yes. Crossplane is a lightweight control plane and runs fine on a local kind cluster for learning and development. It only provisions real OVHcloud infrastructure once you supply valid API credentials through a ProviderConfig, so the control plane itself has no special hosting requirement.

How is provider-ovh different from the OVHcloud Terraform provider?

provider-ovh reuses the OVHcloud Terraform provider's schema under the hood but exposes it as Kubernetes CRDs reconciled continuously by Crossplane. Instead of running terraform apply, you declare resources as YAML and a controller keeps live infrastructure matching that declared state, with RBAC and GitOps built in.