Skip to main content
Deleting a custom resource should not feel like pulling the power cord — it should feel like closing a ticket. Finalizers give controllers a reliable, visible opportunity to perform cleanup before Kubernetes removes the object from etcd. Controllers get one last chance to finish the cleanup they own. If a controller only manages in-cluster children with proper ownerReferences, Kubernetes will garbage-collect those children automatically. But when the controller touches resources outside the cluster — external DNS records, cloud resources, or entries in third-party systems — the API server cannot infer how to undo that work. Finalizers solve this gap. Creating a web app typically tells the controller to create or maintain certain resources. To delete that web app, a user runs:
The delete request changes the resource lifecycle: the API server sets a deletionTimestamp and puts the object into a Terminating state while finalizers remain. Controllers reconcile against that visible terminating object and perform any necessary cleanup before the API server finally removes the object.
The image is a diagram illustrating a Kubernetes concept, where a "WebApp" site manages "Deployment," "Service," and "ConfigMap" components under a hierarchy, with "ownerRef" connections denoting relationships. It emphasizes the lifecycle management aspects of "create" and "delete."
If the controller must manage external resources, add a finalizer so the API server waits while the controller cleans up those outside resources.
The image illustrates a Kubernetes cluster interacting with external components like DNS records, cloud resources, and other systems, highlighting that the API server cannot automatically manage these interactions.
A finalizer is just a string in metadata.finalizers. For example:
While at least one finalizer remains, the API server keeps the resource visible (Terminating) and sets deletionTimestamp. Controllers must reconcile that object and perform cleanup before removing their finalizer. When finalizers are present, reconcile follows two clear paths:
  • Normal path (no deletionTimestamp): ensure resources (in-cluster and external) exist and that your controller’s finalizer is attached before creating anything that requires cleanup later.
  • Cleanup path (deletionTimestamp set): stop creating/updating normal children, perform cleanup of owned external resources, and remove only your controller’s finalizer when cleanup succeeds.
The image is a flowchart titled "Two Paths in Reconcile," depicting decision paths based on whether "deletionTimestamp" is set, leading to a "normal path" or "cleanup path." It includes steps to ensure the finalizer and create/update children and external work.
Example reconcile pseudocode showing the two paths and where to add/remove the finalizer:
When implementing this pattern, follow these guidelines:
  • Add your finalizer before creating external resources — don’t create external state without protecting deletion.
  • Only remove your controller’s finalizer once your cleanup has completed successfully.
  • Make cleanup idempotent: if it runs multiple times (controller retries/crashes), it should safely treat already-deleted resources as success.
  • Record failures in Status, Events, or Logs so operators can diagnose why an object remains Terminating.
If cleanup keeps failing, the resource will remain in Terminating. That is safer than silently leaking external resources, but it requires good observability so humans can take corrective action.
Owner references and finalizers are complementary:
The image is a comparison of Kubernetes tools, "Owner references" and "Finalizers," highlighting their roles in managing resources within a cluster and tasks Kubernetes cannot handle by itself.
Best practices for finalizers and cleanup
  • Use stable, namespaced finalizer names like webapp.kodekloud.com/finalizer to show ownership and avoid collisions.
  • Each controller should only remove the finalizers it added.
  • Ensure cleanup is idempotent and resilient to transient errors.
  • Surface blocking errors via Status, Events, and Logs.
There is an administrative escape hatch when cleanup is irrecoverably stuck: manually remove the finalizer. This forces Kubernetes to forget the object even if external cleanup did not complete.
Force-removing finalizers bypasses cleanup. Use it only as a recovery or emergency action, and after documenting the consequences.
Always add your controller’s finalizer before creating external resources, and make cleanup idempotent so retries and crashes are safe.
Demo overview The accompanying demo implements this pattern in a webapp reconciler: the controller adds a finalizer during normal reconcile, detects deletion via deletionTimestamp, performs a simulated external cleanup, removes its finalizer upon success, and then allows Kubernetes to complete the deletion. Links and references

Watch Video