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

A personal account of CECG's unique onboarding experience, featuring a comprehensive 1-3 month bootcamp that transforms software developers into platform engineers through hands-on IDP projects and mentorship.

Explore the benefits and challenges of multi-tenancy in Kubernetes, with a detailed comparison of different models like multiple clusters, multiple control planes, and shared control planes. This post dives into frameworks such as Vcluster, Kamaji, HNC, and Capsule to help you choose the right approach for your organization.

Explore how to support private service access in GCP from a multi-tenanted Kubernetes platform, comparing IAM Auth & Connectivity with Private Service Access (PSA) to help you choose the right solution for your infrastructure.

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