OutputValues icon
Count icon
Query icon
Log icon
If icon
SlackIncomingWebhook icon
Pause icon
Loop icon
Delete icon
Sleep icon
Fail icon
Schedule icon

Purge aged Kestra execution history behind an approval gate

Delete Kestra executions past a retention window behind a Pause approval, skipping running and protected ones, then re-count and fail if the backlog stays.

Categories
DataInfrastructure

Safety

Deleting execution history is irreversible. A wrong purge loses the only record of what ran, who approved it or what it produced. That is the audit trail an incident review needs. So this flow is read-only until two inputs and a human all agree, it never touches an execution that is still RUNNING or PAUSED. It never touches a protected namespace. It refuses to delete more than the per-run ceiling.

How it works

  1. window computes one UTC cutoff from retention_amount and retention_unit, so every query and every message use the same instant.
  2. aged_before counts the finished executions older than the cutoff with io.kestra.plugin.kestra.executions.Count. That is the storage number you watch.
  3. scan fetches one page of them with io.kestra.plugin.kestra.executions.Query. State is not filtered on the server: the plugin turns a states list into one EQUALS filter per state and the API combines them with AND, so a multi-state list matches nothing.
  4. select filters again client-side instead of trusting the server filters. It keeps only terminated executions that started before the cutoff, drops every namespace in protected_namespaces, drops this flow and this execution, then sorts oldest first.
  5. counts cuts the list at max_deletes and reports the rest as deferred.
  6. With nothing selected, clean logs it and the run ends. That is the normal state.
  7. With something selected, alert posts the list to Slack. The delete happens only when auto_remediate is true, dry_run is false and a human answers the approval Pause with approved: true.
  8. remediate_purge loops the selected ids at concurrencyLimit: 1 and calls io.kestra.plugin.kestra.executions.Delete on each, with its logs, metrics and storage files.
  9. aged_after counts again, reverify queries again, then landed_gate fails the run if any targeted execution is still in the history and backlog_gate fails it if the aged backlog is still over backlog_ceiling.

What you get

A bounded, reviewable retention policy for the one system Kestra does not prune for you. The dry run tells you how much history is over the window and what it would remove, by namespace and flow. The purge is capped per run, so a first pass on an old cluster trims instead of wiping. The re-read after the delete means a refused delete fails the run rather than reading as success.

Who it's for

Platform teams running a long-lived Kestra cluster where the executions page has got slow, the backend database keeps growing. Nobody wants to be the person who deletes the wrong history.

Why orchestrate this with Kestra

Retention is a scheduled decision with a human in the middle. Kestra gives it the Schedule trigger, the typed inputs, the Pause approval with an answer recorded against the execution, the Slack alert on the way in and the failure path on the way out. The Kestra plugin talks to the target cluster over its own API, so the same flow prunes the cluster it runs on or a different one by URL.

Tested end to end

Run against a Kestra 2.0.5 instance with throwaway history, never a cluster holding history worth keeping. The paths exercised: nothing aged so clean fires; aged history present so detection fires; dry_run=true lists and deletes nothing; auto_remediate=true with dry_run=false and an approved Pause deletes and the re-count confirms the drop; a declined Pause deletes nothing; a RUNNING and a PAUSED execution inside the window left in place; an execution in a protected namespace left in place; max_deletes smaller than the backlog deferring the rest; the backlog gate failing the run when too much is left. Confirmed against the target's own API, not only the flow log.

Prerequisites

Kestra 2.0.5 or later, with the Kestra plugin (io.kestra.plugin.kestra) installed.

An API user on the target with permission to search, count and delete executions.

Secrets

Secret Used for
SLACK_WEBHOOK_URL the detection alert and the errors alert
KESTRA_PASSWORD default value of the kestra_password input

Inputs

Input Default What it does
kestra_url http://localhost:8080 the Kestra whose history is purged
retention_amount 30 keep executions newer than this
scan_size 500 executions pulled back per run
retention_unit DAYS MINUTES, HOURS or DAYS
protected_namespaces ["system"] never purged, whatever the age
max_deletes 50 per-run ceiling, the rest is deferred
backlog_ceiling 500 fail the run if this much is still purgeable after the purge
delete_logs true remove the log lines too
delete_metrics true remove the metrics too
delete_storage true remove the internal storage files too
kestra_user admin@kestra.io API username on the target
kestra_password {{ secret('KESTRA_PASSWORD') }} API password on the target
auto_remediate false allow the delete
dry_run true must be false before anything is deleted

Quick start

  1. Add SLACK_WEBHOOK_URL and KESTRA_PASSWORD as secrets.
  2. Run it as it is. Read the summary log: it gives the cutoff, the aged count on the instance, the purgeable count and the first max_deletes ids with their namespace, flow and end date.
  3. Add any namespace whose history you must keep to protected_namespaces.
  4. Set retention_amount and max_deletes to numbers you would sign off, then run with auto_remediate: true and dry_run: false and answer the Pause.
  5. Enable the Schedule trigger once the dry run output looks right. Its inputs keep it read-only, so change them deliberately.

How to extend

Point kestra_url at another cluster to prune it from one place. Narrow the scan with the Query task's namespace or flowId property to run a different retention per team. Raise scan_size on a cluster whose aged history is deeper than one page. Set delete_storage: false where object storage has its own lifecycle rule. Swap the Slack task for any notification plugin. Add a second Count before the purge with namespaces set to one team to report their share of the backlog.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.