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 comprehensive journey through the Google Cloud Professional DevOps Exam, exploring how CECG's platform engineering training and real-world project experience provided the practical foundation needed for certification success.

A deep dive into mise and nix-shell, two modern tools for managing development environments. This post compares their philosophies, features, and practical applications to help you choose the right solution for ensuring consistency and reproducibility in your projects.

Learn how we implemented identity-based authentication for a developer platform using Google Identity-Aware Proxy (IAP) on GKE. This post covers our technical approach, from ingress architecture to overcoming IAP limitations, to provide secure, seamless access to internal services.

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