UXP/Crossplane version upgrade fails in Spaces with "not a supported crossplane version" webhook error

Last updated: September 30, 2026

Issue

When attempting to upgrade the UXP/Crossplane version on a Spaces control plane (e.g. from 1.20.10-up.3 to 2.0.8-up.4), the upgrade fails. ArgoCD reports an error similar to:

error when patching "/dev/shm/<id>": admission webhook "controlplanes.spaces.upbound.io" denied the request: spec.crossplane.version "2.0.8-up.4" is not a supported crossplane version

The crossplane-versions-public ConfigMap does not list the target version, even though the image appears to be present in the registry.


Environment

  • Spaces v1.17.1 (Self-Hosted)

  • UXP/Crossplane upgrade targeting v2.x (e.g. 2.0.8-up.4)

  • Air-gapped or mirrored registry setup

  • ArgoCD used for GitOps deployment

  • EKS (or similar Kubernetes distribution)

Commonly triggered when upgrading from Crossplane 1.x to 2.x in an environment with a private/mirrored registry.


Resolution

Step 1: Prevent auto-upgrades from overriding Git as the source of truth

Set the auto-upgrade channel to None on your ControlPlane to ensure Git controls the version:

spec:
  crossplane:
    autoUpgrade:
      channel: None
    version: <TARGET_VERSION>

Step 2: Prevent ArgoCD from overwriting the versions ConfigMap

Add the following ignoreDifferences rule to your Spaces ArgoCD Application to stop Argo from reverting the ConfigMap to the chart's default list:

spec:
  ignoreDifferences:
    - group: ""
      kind: ConfigMap
      name: crossplane-versions-public
      namespace: upbound-system
      jsonPointers:
        - /data/versions
        - /data/latest
  syncPolicy:
    syncOptions:
      - RespectIgnoreDifferences=true

Step 3: Verify what is present in your registry

Run the following commands, replacing <YOUR_REGISTRY> with your registry path (e.g. my-registry.example.com/spaces-artifacts):

REG=<YOUR_REGISTRY>
crane manifest $REG/crossplane:v<TARGET_VERSION>
crane manifest $REG/controller-manager:v<TARGET_VERSION>
crane manifest $REG/crossplane:<TARGET_VERSION>

The first two are the images (prefixed with v). The third is the chart tag (without v). Note which of these return 404 or MANIFEST_UNKNOWN.

Step 4: Mirror any missing artifacts - images first, chart last

crane copy xpkg.upbound.io/spaces-artifacts/crossplane:v<TARGET_VERSION> $REG/crossplane:v<TARGET_VERSION>
crane copy xpkg.upbound.io/spaces-artifacts/controller-manager:v<TARGET_VERSION> $REG/controller-manager:v<TARGET_VERSION>
crane copy xpkg.upbound.io/spaces-artifacts/crossplane:<TARGET_VERSION> $REG/crossplane:<TARGET_VERSION>

Always mirror the images (v-prefixed tags) before the chart tag (without v).

Step 4: Force the Spaces versions controller to re-read the registry

kubectl -n upbound-system annotate cm crossplane-versions-public \
  spaces.upbound.io/refresh="$(date +%s)" --overwrite

kubectl -n upbound-system get cm crossplane-versions-public \
  -o jsonpath='{.data.versions}'

Confirm that the target version (without the v prefix, e.g. 2.0.8-up.4) now appears in the output before proceeding.

Step 6: Re-sync the ControlPlane in ArgoCD

Use spec.crossplane.version: <TARGET_VERSION> (without the v prefix) in your ControlPlane manifest.

Step 7: Upgrade sequentially across minor versions

When upgrading across multiple minor versions (e.g. 2.0 → 2.1 → 2.2), upgrade one minor version at a time. Do not skip minor versions.


Cause

The Spaces admission webhook only accepts Crossplane versions that are present in the crossplane-versions-public ConfigMap. This ConfigMap is populated by the Spaces versions controller by scanning chart tags (without the v prefix) in the configured registry.

In Crossplane 1.x, Helm charts and container images were stored in separate repositories. In Crossplane 2.x, both the chart and images reside in the same crossplane repository but under different tags:

  • Images use a v-prefixed tag (e.g. crossplane:v2.0.8-up.4)

  • The chart uses a tag without v (e.g. crossplane:2.0.8-up.4)

Mirroring pipelines that only track image tags will copy the v-prefixed tags but miss the chart tag. Because the versions controller looks for the chart tag, the version never appears in the ConfigMap, and the webhook rejects the upgrade request.

Additionally, the ControlPlane CRD also defaults autoUpgrade.channel to Stable, so any manifest that only sets crossplane.version is affected. Spaces may auto-upgrade the control plane to the latest stable version, ArgoCD then tries to revert it to the specified version, and the webhook rejects that change. Set autoUpgrade.channel: None to avoid this.

2.x control planes also need a new image, controller-manager:v2.0.8-up.4, from its own controller-manager repository.