New to Kestra?
Use blueprints to kickstart your first workflows.
A Kestra blueprint that cleans up old completed Kubernetes Jobs and posts a Slack summary, with a safe dry-run default.
list_jobs (io.kestra.plugin.scripts.shell.Commands) runs kubectl get jobs in the target namespace and saves the full job list as JSON. It authenticates with a client certificate and key passed as Kestra secrets, the same pattern the Kubernetes gallery blueprints use.find_stale (io.kestra.plugin.scripts.python.Script) checks every job: only jobs with a Complete=True condition and a completionTime older than retention_days count as stale. Failed jobs are never touched, they stay for investigation. It also writes the stale names to stale.txt and builds the Slack summary.maybe_clean (io.kestra.plugin.core.flow.If) runs only when something is stale. Inside it, do_clean runs only in live mode (dry_run: false) and deletes each stale job with kubectl. send_summary always posts the report to Slack: what would be deleted in dry-run mode, what was deleted in live mode.kubectl delete.dry_run is true unless you explicitly turn it off, failed jobs are never deleted, and the retention window guards fresh jobs.Anyone running batch Jobs on Kubernetes: migrations, backups, report generators, data loads. Completed jobs pile up forever by default and clutter kubectl get jobs until nobody can find the real failures. Works on any cluster you can reach with a client certificate: minikube, kind, managed clusters.
A shell script on a cron server can delete old jobs, but then you need somewhere to hold the secrets, a place to see the run history when something got deleted that should not have been, and a safe dry-run mode you can trust. Kestra gives you all three: secrets stay out of the YAML, every run keeps its logs and outputs where you can audit them, and the dry-run default means the first run can never surprise you.
| Secret | What it is |
|---|---|
CLIENT_CRT |
base64-encoded client certificate for the cluster |
CLIENT_KEY |
base64-encoded client key for the cluster |
MASTERURL |
base64-encoded Kubernetes API server URL, reachable from inside the Kestra container |
SLACK_WEBHOOK |
base64-encoded Slack Incoming Webhook URL |
In Kestra OSS, secrets come from environment variables prefixed with SECRET_ and must be base64-encoded.
SECRET_ environment variables.retention_days: 0 and dry_run: true: the Slack report lists what would be deleted, and nothing is touched.kubectl create job jj-clean --image=busybox -- sh -c 'echo done'), wait for it to complete, run again in dry-run mode: it shows up in the report.dry_run: false: the job is deleted and the Slack report says so. Run again: quiet, nothing stale left.cron trigger.| Input | Type | Default | What it does |
|---|---|---|---|
namespace |
STRING | default |
the namespace whose jobs to clean |
retention_days |
INT | 7 |
completed jobs older than this many days get deleted |
dry_run |
BOOL | true |
when true, only reports what would be deleted without deleting anything |
slack_webhook |
STRING | (required) | Incoming webhook URL of the Slack channel for the cleanup report |
dry_run defaults to true for a reason. Flip it to false only after you have seen a dry-run report and you trust the list.Complete=True are ever deleted. Failed jobs are left alone because they might be evidence for an ongoing investigation.completionTime is skipped. This should not happen for completed jobs, but the guard is there.MASTERURL is reachable from inside the Kestra container. localhost inside the container is not your host: use host.docker.internal when Kestra runs in Docker on the same machine as the cluster.