Learn about Crossplane's deletion policies and how improper handling can lead to orphaned cloud assets. This guide covers Kubernetes Admission Protection, Crossplane Delete Policies, Usages for Dependency Ordering, and more to help you manage your infrastructure safely.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "database-deletion-protection"
spec:
matchConstraints:
resourceRules:
- apiGroups: ["contoso.com"]
apiVersions: ["v1beta1"]
operations: ["DELETE"]
resources: ["databases"]
validations:
- expression: "oldObject.metadata.annotations['contoso.com/delete-protected'] == 'true'"
messageExpression: "'This database is protected from deletion. Remove the contoso.com/delete-protected annotation or set it to false to allow deletion'"
reason: Forbidden
failurePolicy: Fail
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "database-deletion-protection-binding"
spec:
policyName: "database-deletion-protection"
validationActions: [Deny]
matchResources:
resourceRules:
- apiGroups: ["contoso.com"]
apiVersions: ["v1beta1"]
resources: ["databases"]
defaultCompositeDeletePolicy: Foreground to the CompositeResourceDefinition:apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xdatabases.platform.example.com
spec:
defaultCompositeDeletePolicy: Foreground
# ... rest of spec
apiVersion: platform.example.com/v1alpha1
kind: Database
metadata:
name: my-database
spec:
compositeDeletePolicy: Foreground
# ... rest of spec
flowchart TD
XR["XR"] -- delete --> Usage
XR -- delete --> Cluster
Usage -. blocks .-> Cluster
Usage -. waits for .-> Release
XR -- delete --> Release
style Release fill:#e06666,stroke:#333,stroke-width:2px
flowchart TD
XR["XR"] -- waiting to delete --> Usage
XR -- blocked from deleting --> Cluster
Usage -. blocks .-> Cluster
style Usage fill:#e06666,stroke:#333,stroke-width:2px
flowchart TD
XR -- deleting --> Cluster
style Cluster fill:#e06666,stroke:#333,stroke-width:2px
compositeDeletePolicy: Foreground is used because it wouldn’t block deletion of its child resources before its own deletion with the default deletion policy Background.apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Usage
metadata:
name: protect-database-from-cluster-deletion
spec:
of:
apiVersion: platform.example.com/v1alpha1
kind: XDatabase
resourceRef:
name: my-production-database
by:
apiVersion: platform.example.com/v1alpha1
kind: XCluster
resourceRef:
name: my-k8s-cluster
spec.replayDeletion: true to trigger an immediate retry of blocked deletions instead of waiting for exponential back-off.managementPolicies like so:apiVersion: logging.gcp.upbound.io/v1beta2
kind: ProjectSink
metadata:
name: default-project-sink
annotations:
crossplane.io/external-name: _Default
spec:
managementPolicies: [Observe, Create, Update, LateInitialize]
forProvider:
project: my-project-id
apiVersion: logging.gcp.upbound.io/v1beta2
kind: ProjectSink
metadata:
name: default-project-sink
annotations:
crossplane.io/external-name: _Default
spec:
managementPolicies: [Observe]
forProvider:
project: my-project-id
--enable-management-policies. Check your specific provider documentation..pre-commit-config.yaml file in your git repo:repos:
- repo: local
hooks:
- id: crossplane-deletion-policy-check
name: Check Crossplane deletion policies
entry: bash -c 'find . -name "*.yaml" -exec grep -l "kind.*CompositeResourceDefinition" {} \; | xargs grep -L "defaultCompositeDeletePolicy" && echo "ERROR: XRD missing defaultCompositeDeletePolicy" && exit 1 || exit 0'
language: system
files: '\.ya?ml$'
# Check what finalizers exist first
kubectl get database my-stuck-database -o jsonpath='{.metadata.finalizers}'
# Remove finalizers from a stuck Crossplane resource
kubectl patch database my-stuck-database -p '{"metadata":{"finalizers":[]}}' --type=merge
# Install the crossplane CLI tool from https://docs.crossplane.io/latest/cli/
crossplane beta trace database.my-company.com -n my-namespace my-stuck-database
# Check the resource status
kubectl describe database.gcp.upbound.io my-stuck-database
# Check provider logs
kubectl get pods | grep "provider-gcp-container" # CHANGE FOR THE PROVIDER MANAGING YOUR RESOURCE
kubectl logs provider-gcp-container-xxx -f
# Look for related events
kubectl get events --field-selector involvedObject.name=my-stuck-database
kubectl get events | grep my-stuck-database
This article is provided as a general guide for general information purposes only. It does not constitute advice. CECG disclaims liability for actions taken based on the materials.
Discover more insights from our blog collection

Learn how we successfully introduced 1000+ platform users to Horizontal Pod Autoscaling through an interactive knowledge platform with hands-on learning modules.

Learn how to monitor an MVP Kubernetes-based developer platform using SLOs and SLIs. This post outlines a structured approach to defining measurable reliability targets for the control plane, data plane, networking, and load balancing to ensure platform stability and tenant satisfaction.

Introducing semver-utils, an open-source tool for streamlined semantic versioning from automated pipelines. This post covers how to fetch and create semantic version tags in Git repositories, manage multiple version sets with prefixes, and integrate with CI/CD workflows.

Want To Talk This Through?
Design Safe Deletion Policies and Avoid Orphaned Cloud Resources. Talk to Us About Hardening Your Crossplane Setup.