A Kubernetes controller that schedules resource deletion and Pod template updates through time-based annotations. It supports namespaced and cluster-scoped resources that expose the required API operations.
Use it to schedule resource deletion or request application restarts without a separate CronJob or wrapper object. Pod replacement depends on the workload's update behavior. See Restart mechanism.
This example uses a Deployment named example-app in default namespace, without existing lifecycle action annotations. Actions are limited to Deployments in default namespace.
helm repo add lifecycle-controller https://cstanislawski.github.io/lifecycle-controller
helm repo update
helm install lifecycle-controller lifecycle-controller/lifecycle-controller \
--namespace lifecycle-controller \
--create-namespace \
--set 'controllerManager.scope.watchResources[0]=deployments.apps' \
--set 'controllerManager.scope.watchNamespaces[0]=default'
kubectl rollout status deployment/lifecycle-controller -n lifecycle-controllerPreview a restart after one minute:
kubectl annotate deployment example-app -n default \
lifecycle.cezary.dev/dry-run="true" \
lifecycle.cezary.dev/restart-after="1m" --overwrite
kubectl logs deployment/lifecycle-controller -n lifecycle-controller --since=5m
kubectl get events -n default \
--field-selector involvedObject.kind=Deployment,involvedObject.name=example-app \
--sort-by=.lastTimestampCheck for the planned restart-after conversion in the logs and a DryRunRestart Event. Enable the action:
kubectl annotate deployment example-app -n default \
lifecycle.cezary.dev/dry-run="false" --overwriteThe controller then converts restart-after to restart-at. After the scheduled restart time, check for a RestartTriggered Event and the Pod template's lifecycle.cezary.dev/restartedAt annotation. Use kubectl rollout status deployment/example-app -n default to check the workload after the template update.
To delete feature-branch resources after three days, apply a delete-after annotation to each resource.
apiVersion: apps/v1
kind: Deployment
metadata:
name: feature-branch-x-backend
namespace: dev-features
annotations:
lifecycle.cezary.dev/delete-after: "3d" # Deletes this deployment after 3 days
spec:
# ...To request a restart each day at 03:00 in a local timezone, use restart-cron and cron-timezone.
apiVersion: apps/v1
kind: Deployment
metadata:
name: legacy-app
namespace: production
annotations:
lifecycle.cezary.dev/restart-cron: "0 3 * * *" # Daily at 3:00 AM
lifecycle.cezary.dev/cron-timezone: "America/New_York"
spec:
# ...Use restart-at for a fixed restart time. For a restart after a database migration, use a workflow that waits for migration completion before applying the annotation.
apiVersion: apps/v1
kind: Deployment
metadata:
name: application
namespace: production
annotations:
lifecycle.cezary.dev/restart-at: "2026-10-10T03:00:00Z"
spec:
# ...-
For all watched resources:
lifecycle.cezary.dev/reference-point(string) - specifies the starting point for relative duration timers (for-afterannotations).applyTimestamp(default) - the timer starts when the controller processes the-afterannotation. Re-applying the manifest resets the timer ("keep-alive" behavior).creationTimestamp- the timer starts from the resource's creation time. This creates a fixed TTL that is not affected by subsequent updates.
lifecycle.cezary.dev/delete-at- absolute TTL. The controller deletes the resource at or after this specific date and time (e.g.,2024-12-31T23:59:59Z).- The value must be an RFC3339 timestamp with explicit timezone offset (
ZorΒ±hh:mm). - This can be applied directly to a
Namespaceto trigger its deletion. Kubernetes will handle the subsequent removal of all resources within that namespace.
- The value must be an RFC3339 timestamp with explicit timezone offset (
lifecycle.cezary.dev/delete-after- relative TTL (e.g.,5m,1h,3d). The controller calculates a deletion time from the selected reference point, saves it aslifecycle.cezary.dev/delete-at, and removesdelete-after.lifecycle.cezary.dev/catch-up- overrides the global policy for a missed deletion or restart. Acceptsnever,always, or a positive duration (e.g.,15m). See Missed actions.lifecycle.cezary.dev/dry-run- enables dry-run for a resource. Accepts standard boolean values such astrue,false,1, and0. Invalid values create a warning Event and no action is taken.lifecycle.cezary.dev/managed-by: "lifecycle-controller"- added by the controller when it mutates a resource, such as converting*-afterannotations to*-atannotations, maintaining restart schedule state, or triggering a restart.
-
For watched Deployments, StatefulSets, and DaemonSets in the
appsAPI group:lifecycle.cezary.dev/restart-at- requests one Pod template update at or after a specific date and time.- The value must be an RFC3339 timestamp with explicit timezone offset (
ZorΒ±hh:mm).
- The value must be an RFC3339 timestamp with explicit timezone offset (
lifecycle.cezary.dev/restart-after- requests one Pod template update after a relative duration (e.g.,1h). The controller converts this to an absoluterestart-atannotation.lifecycle.cezary.dev/restart-every- requests recurring Pod template updates at a relative interval (e.g.,7dfor weekly updates), with a minimum duration of1m.lifecycle.cezary.dev/restart-cron- requests recurring Pod template updates based on a standard five-field, minute-resolution cron expression (e.g.,"0 3 * * *"for daily at 03:00).lifecycle.cezary.dev/cron-timezone- optional timezone forrestart-crononly. Must be valid IANA timezone (e.g.,America/New_York). Defaults toUTC.- Other resources receive an
UnsupportedRestartKindwarning Event and their restart annotations remain unchanged.
delete-after, restart-after, restart-every, and duration values for catch-up require exactly one positive integer and unit, without signs or spaces. Valid units are s, m, h, and d. Values such as 1d, 25h, and 90m are valid.
Zero, compound values (e.g., 1d2h), arithmetic expressions (e.g., 1d-25h), decimal values (e.g., 1.5h), and values that exceed the Go duration limit are invalid. Invalid values record an InvalidAnnotation warning Event and leaves the resource unchanged.
Actions can run after their scheduled time. Restarts update the Pod template. The workload's update strategy determines whether Pods are replaced. See Controller behavior for more information.
By default, the Helm chart watches standard workloads, ConfigMaps, Services, Ingresses, NetworkPolicies, PersistentVolumeClaims, HorizontalPodAutoscalers, PodDisruptionBudgets, Namespaces, and PersistentVolumes. It grants get, list, watch, patch, update, and delete for those resources, plus Event permissions and permissions for enabled leader-election and secure-metrics features.
A list of filtered namespaces creates a Role and RoleBinding in each watched namespace. Namespace patterns or leaving the list unset results in a ClusterRole. Namespace objects still require cluster permissions.
For resource patterns such as deploy*.apps, set rbac.create: false and supply your own RBAC with explicit resource names.