Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Infrastructure as Code By James Joyner IV · · 9 min read

Crossplane Error: 'cannot establish control of object: existing object is not controlled by this composite resource' — Cause, Fix, and Troubleshooting Guide

Quick answer

Fix Crossplane 'cannot establish control of object: existing object is not controlled by this composite resource' when two XRs or an adopted resource collide.

  • #iac
  • #infrastructure-as-code
  • #crossplane
  • #troubleshooting
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

Crossplane enforces single ownership of every managed resource (MR). Each MR carries a controller owner reference pointing back to exactly one composite resource (XR). When a composition tries to render or claim ownership of an MR that already has a different controller owner reference — or no Crossplane owner reference at all — the reconciler refuses to take control and reports:

cannot establish control of object: existing object is not controlled by this composite resource

You will see it in the XR or claim events, and the affected MR stays SYNCED=False. The reconcile loop retries forever, so the resource never progresses. It shows up in three situations: two Compositions/XRs both render an MR with the same external-name or deterministic name; someone tried to “adopt” a hand-created cloud resource into Crossplane; or an XR was recreated (new UID) while the old MR still references the previous owner UID.

$ kubectl describe bucket my-app-bucket

Events:
  Type     Reason                 Message
  ----     ------                 -------
  Warning  CannotInitializeManagedResource  cannot establish control of object:
    existing object is not controlled by this composite resource

Symptoms

  • An MR (Bucket, RDSInstance, Subnetwork, etc.) is stuck SYNCED=False, READY=False.
  • XR/claim events show cannot establish control of object.
  • Two XRs list the same external-name annotation on their MRs.
  • The MR’s .metadata.ownerReferences[].uid does not match the current XR’s .metadata.uid.
  • Deleting and recreating the claim did not help — the underlying MR persisted.

Common Root Causes

1. Two Compositions render an MR with the same deterministic name

If two XRs patch the same crossplane.io/external-name (or produce the same metadata.name), the second XR’s MR collides with the first XR’s object.

# Both compositions patch external-name to a fixed value — collision
- type: FromCompositeFieldPath
  fromFieldPath: spec.bucketName   # same value for two claims
  toFieldPath: metadata.annotations[crossplane.io/external-name]

2. Adopting a pre-existing cloud resource without the right owner reference

Setting crossplane.io/external-name to an existing bucket makes the provider observe it, but the in-cluster MR object may already exist from a prior import owned by another XR.

3. XR was recreated and the MR kept the old owner UID

Deleting a claim with Orphan deletion policy leaves the MR behind. A new claim with the same name gets a new UID, but the orphaned MR still references the old UID.

ownerReferences:
- uid: 9f1c-OLD-UID   # points at the deleted XR, not the new one

4. A managed resource created directly (not through a composition)

Someone applied a Bucket MR by hand, then wrote a Composition that renders the same object. The hand-applied object has no composite owner reference at all.

5. Two Crossplane control planes or namespaces sharing one cloud account

Two clusters both reconciling the same external-name fight over the same cloud object; whichever wrote the owner reference first wins, the other errors.

How to Diagnose

Identify the conflicting MR and its owner

kubectl get managed
kubectl describe bucket my-app-bucket

Compare owner UID against the current XR

# UID the MR thinks owns it
kubectl get bucket my-app-bucket -o jsonpath='{.metadata.ownerReferences[*].uid}{"\n"}'

# UID of the XR that is trying to control it
kubectl get xbuckets.example.com -o jsonpath='{.items[*].metadata.uid}{"\n"}'

If these differ, you have a stale/foreign owner reference.

Find duplicate external-names

kubectl get managed -o custom-columns=\
'NAME:.metadata.name,EXT:.metadata.annotations.crossplane\.io/external-name'

Two rows with the same EXT value are the collision.

Watch the reconcile in real time

crossplane beta trace xbuckets.example.com/my-app -o wide
kubectl -n crossplane-system logs deploy/provider-aws-s3 -f | grep establish

Fixes

Resolve a stale owner reference (recreated XR)

If the MR is genuinely meant for the new XR, remove the dangling owner reference so the current XR can adopt it:

kubectl patch bucket my-app-bucket --type=json \
  -p='[{"op":"remove","path":"/metadata/ownerReferences"}]'

On the next reconcile the correct XR sets its own owner reference. Only do this once you have confirmed the old XR is gone.

Break a duplicate-name collision

Make each XR produce a unique external-name. Include the XR name or a hash rather than a fixed field:

- type: CombineFromComposite
  combine:
    variables:
      - fromFieldPath: spec.bucketName
      - fromFieldPath: metadata.uid
    strategy: string
    string:
      fmt: "%s-%.8s"
  toFieldPath: metadata.annotations[crossplane.io/external-name]

Cleanly re-adopt a hand-created resource

Delete the un-owned MR object from the cluster (with Orphan so the cloud resource survives), then let the composition recreate it with proper ownership:

kubectl patch bucket my-app-bucket \
  -p '{"spec":{"deletionPolicy":"Orphan"}}' --type=merge
kubectl delete bucket my-app-bucket
# composition re-renders the MR, observes the existing cloud bucket, owns it

Stop two control planes fighting

Give each control plane a distinct naming prefix or a dedicated cloud account/project so their external-name values can never overlap.

What to Watch Out For

  • Never patch a fixed, shared value into crossplane.io/external-name across multiple claims — always incorporate a unique component.
  • Prefer deletionPolicy: Delete unless you have a deliberate import/adopt workflow; orphans are the most common source of this error.
  • When importing existing infrastructure, import into exactly one XR and document it.
  • After recreating an XR, verify owner UIDs match before assuming the platform will self-heal.
  • Keep one Crossplane control plane authoritative per cloud account to avoid cross-cluster ownership races.
Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.