Schedule icon
Get icon
Queries icon
If icon
Log icon
Loop icon
Request icon
PurgeStorage icon
OutputValues icon
Pause icon
Fail icon
Dag icon
PurgeExecutions icon
PurgeLogs icon
PurgeKV icon
Set icon
Delete icon
SlackIncomingWebhook icon

Kestra instance retention policy with dry run, approval and audit CSV

Safe, auditable Kestra retention per namespace. Dry-run counts, approval gate, PurgeExecutions, PurgeLogs, PurgeKV and an audit CSV of every deletion.

Categories
CoreInfrastructure

Every self-hosted Kestra instance slowly fills its database and internal storage with old executions, logs, metrics and KV entries. This flow is a retention policy you can hand to operators: one JSON rule per namespace (production keeps more than dev), a dry run by default that only counts, an approval before large deletions, and an audit CSV of what was or would be deleted per namespace and data type. It runs against the real Kestra API of the instance it lives on; nothing is simulated. The default rules are sample values for company.prod, company.team and company.dev: replace them with your own.

How it works

  1. A monthly Schedule (or a manual run) starts the flow. Flow checks block the execution before it is created when retention_rules is not a non-empty array, or when a real purge has a rule with an empty namespace.
  2. plan (DuckDB) validates every rule and fails the run listing all problems: unknown keys (a typo such as execution_keep_days), negative or missing days, logs kept longer than executions, non-terminal purge_states, duplicate namespaces and overlapping rules (company would also purge company.prod, because purges include child namespaces). It computes every cutoff from one anchor, the schedule date or the execution start.
  3. count_before (Loop) asks the Kestra API, per namespace, how many executions, hidden Loop iterations, log lines and DEBUG/TRACE lines match the filters the purge tasks use. preview_orphan_files uses the native dryRun: true of PurgeStorage. log_preview prints the counts.
  4. A real purge above approval_threshold pauses in request_approval with an approve and reason form. A rejection, or 8 hours without an answer, ends the run with nothing deleted.
  5. real_purge runs one namespace at a time, only for namespaces whose count succeeded, then recounts what is left.
  6. Failed namespaces go to a KV dead-letter list with a 40-day ttl; mode: replay reprocesses only them. finally always writes the audit CSV, and afterExecution sends the outcome.

What each purge removes

Every deletion is irreversible (there is no recycle bin): keep a database backup if you may need the history.

  • purge_executions (PurgeExecutions): execution records of the listed terminal states that ended before the cutoff, with their task and flow outputs, metrics and internal-storage files, Loop iteration sub-executions included. Running and paused executions never match.
  • purge_logs and purge_debug_logs (PurgeLogs): execution and trigger log lines up to the cutoff; the second only TRACE and DEBUG lines in the shorter window.
  • purge_expired_kv (PurgeKV, expiredOnly: true): KV entries whose TTL has passed. They are already unreadable, but the old value is gone.
  • sweep_orphan_files (PurgeStorage, opt-in): execution files older than the cutoff that no purge reached. Rules that keep some states are refused, because the sweep would delete the files of kept executions.

Flows, namespace files, secrets and live KV keys are never touched.

What this blueprint teaches

  • Flow checks (checks): reject unsafe input before an execution exists. The second check exists because PurgeExecutions and PurgeLogs treat a missing namespace as every namespace.
  • Loop iterations are isolated sub-executions (count_before, purge): transmitFailed: false and Loop outputs collect one row per namespace, failed ones included, so one bad namespace never stops the others.
  • If around a destructive Loop (real_purge): on Kestra 2.0.5 a Loop with runIf: false is marked SKIPPED but its iterations still run. scope_guard re-checks the dry-run flag inside each iteration.
  • Dag (apply_rule): the KV cleanup runs alongside; logs, DEBUG logs and the file sweep wait for purge_executions, so a failed execution purge never leaves executions without their logs.
  • Pause with onResume (request_approval, check_decision): a human decision with a reason, and behavior: FAIL when nobody answers.
  • KV ttl dead-letter and replay (remember_failed, failed_last_run, mode): the list expires on its own after 40 days, and a clean run deletes it.
  • finally (write_audit): the audit CSV is written after a success, a rejection, a validation error or a partial failure.
  • errors with errorLogs() (log_failure, alert_failure): failed task id, message and execution link.
  • sla and afterExecution (max_runtime, notify_outcome): on 2.0.5 an SLA breach fails the run at once and skips finally, so afterExecution, which runs once the final state is known, is the hook that reports it (label retention_sla: breached).
  • concurrency (limit: 1, FAIL): a second run fails immediately instead of purging in parallel. Loop iterations do not count against the limit.

What you get

  • A dry-run report per namespace before anything is deleted, then deleted and remaining counts.
  • An audit CSV (audit_csv output) with namespace, data type, keep days, cutoff, matched, deleted, remaining and status.
  • Loop iteration executions counted, which the default search and the UI list leave out.
  • Idempotent runs: a second run finds nothing to delete.

Who it's for

  • Platform teams and administrators running a self-hosted Kestra OSS instance.
  • Operators who need a reviewable retention policy per environment.

Why orchestrate this with Kestra

The policy is versioned YAML, the run history is its own audit trail, and approvals, retries, timeouts, alerts and replay are declared, not hand-written around a cron job running DELETE statements.

Prerequisites

  • Kestra 2.0 with the DuckDB and Slack plugins, and an API user that can read executions and logs.
  • kestra_api_url reachable from the worker (http://localhost:8080 on a single-node install).

Secrets

  • KESTRA_API_USERNAME and KESTRA_API_PASSWORD: basic-auth user for the count requests (required).
  • SLACK_WEBHOOK_URL: alert and outcome messages (optional; every message is also in the logs).

Quick start

  1. Add the secrets and replace the sample retention_rules with your namespaces and days.
  2. Run with the defaults and read log_preview and the audit CSV.
  3. Run with dry_run: false once the counts look right, and approve if asked.
  4. Add inputs: {dry_run: false} to the monthly trigger to enforce the policy every month.

How to extend

  • Keep failed executions longer with a purge_states list, as in the company.prod sample rule.
  • Add PurgeFiles for old namespace file versions, or copy the audit CSV to object storage.
  • Raise concurrencyLimit on purge if your database handles parallel purges.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.