New to Kestra?
Use blueprints to kickstart your first workflows.
Detect MongoDB documents past a retention window, alert Slack, then delete them behind an approval and re-check that none remain.
MongoDB has one built-in way to drop old documents: a TTL index on a date field. When that index was never created or someone dropped it, nothing deletes the old documents. The collection grows without bound and data lives well past the policy that was supposed to remove it. A storage alert only fires once the disk is already a problem.
This blueprint reads the collection with a date filter, counts the documents older than the
retention window and reports them. Behind a safe mode gate and an approval it deletes them
with a single DELETE_MANY, then reads the collection a second time with the same filter
and fails the run if any expired document is still there.
This blueprint was created by zkasuran.
auto_remediate true, dry_run false, then a human answering the Pause. Three
conditions, all of them, before a document is removed.scan_limit, so a huge expired set
cannot exhaust the worker. The delete is a server-side DELETE_MANY, so it is not capped
and removes every matching document in one call.detect (io.kestra.plugin.mongodb.Find) selects documents whose timestamp_field is
before the retention cutoff, oldest first, up to scan_limit. The cutoff is
execution.startDate minus retention_days days, frozen for the whole run so detect,
delete and the re-check all use one value.report (io.kestra.plugin.core.log.Log) logs the count. Task outputs are not in the
execution JSON, so the number the gate acts on is written to the log.route (io.kestra.plugin.core.flow.If): zero expired documents logs clean and stops.
One or more sends alert to Slack and prints planned, which states what a remediation
run would do on every run including a dry one.remediate_gate (io.kestra.plugin.core.flow.If) opens only with auto_remediate true
and dry_run false. hold (io.kestra.plugin.core.flow.Pause) waits for an answer.delete (io.kestra.plugin.mongodb.Delete, DELETE_MANY) removes the
documents, reverify (io.kestra.plugin.mongodb.Find) runs the same filter again and
verify_gate fails the run with io.kestra.plugin.core.execution.Fail if the re-check
still finds any. Without approval declined records the decision and nothing changes.errors posts to Slack when any task fails, for example when MongoDB is unreachable.A TTL index deletes documents quietly and gives no report, no approval step and no proof
the policy held. This flow makes each of those explicit. The schedule decides when the
policy is enforced, the detection count is logged, the destructive half sits behind
auto_remediate, dry_run and a Pause with the decision recorded on the execution. The
re-read turns the delete into a checked outcome. The Slack alert and the error path are
the same ones the rest of your flows use.
On Kestra 2.0.5 OSS against a MongoDB 7.0 container, on the final file. The collection was
seeded with 5 documents dated 60 days old and 3 dated 5 days old, timestamp field
createdAt. Every before and after number was read back with mongosh, not only from the
Kestra log.
| Run | Inputs | Result |
|---|---|---|
| Clean | retention_days: 90 |
0 expired. clean logged, no Slack alert, SUCCESS. mongosh count still 8 |
| Detect | retention_days: 30 |
5 expired. alert to Slack, SUCCESS. mongosh confirms 5 older than the cutoff |
| Dry run | retention_days: 30, defaults |
Reports 5, planned says it would delete, nothing deleted. mongosh count still 8 |
| Armed but dry | auto_remediate: true, dry_run: true |
Still no delete. mongosh count still 8 |
| Declined | auto_remediate: true, dry_run: false, resumed approved: false |
Paused, then declined. mongosh count still 8 |
| Remediate | auto_remediate: true, dry_run: false, resumed approved: true |
Deleted 5, re-check 0, SUCCESS. mongosh count 3, all recent |
| Still dirty | the same, with a writer inserting old-dated documents during the run | Delete ran, re-check found documents still expired, verify_gate failed the run and errors alerted |
| Unreachable | connection_uri pointed at a dead host |
detect failed, errors posted to Slack, execution FAILED |
The clean run and the detect run isolate that. Nothing on the collection changed between
them except the retention window: 90 days returned 0 expired, 30 days returned 5, on the
same 8 documents. mongosh confirmed it directly with a $lt count against each cutoff.
The remediate run then removed those 5 and the re-read returned 0. The still-dirty run
proved the gate the other way, failing when documents older than the window remained.
docker run -d --name mdb --network mynet mongo:7.0, seed a mix of old and
recent documents, then run the flow with connection_uri: mongodb://mdb:27017.MONGODB_URI: the MongoDB connection string. Read when connection_uri is left empty,
which is how the flow should run in production. In Kestra OSS, provide it as the
base64-encoded environment variable SECRET_MONGODB_URI.SLACK_WEBHOOK_URL: used by alert and the errors block. Provide it as
SECRET_SLACK_WEBHOOK_URL.| Input | Default | Purpose |
|---|---|---|
connection_uri |
empty | Plain connection string for a local test. Empty reads MONGODB_URI |
database, collection |
appdata, events |
The collection to guard |
timestamp_field |
createdAt |
The BSON date field documents are aged on |
retention_days |
30 |
Documents older than this many days are expired |
scan_limit |
1000 |
Ceiling on the detect read and each re-check |
approval_timeout |
PT2H |
How long the pause waits for an answer |
auto_remediate |
false |
Allow the delete |
dry_run |
true |
Must be false for a delete to apply |
MONGODB_URI and SLACK_WEBHOOK_URL secrets.database, collection and timestamp_field, then run the flow. It reports the
expired count and stops.auto_remediate: true and dry_run: false, run it again, then
answer the pause. The delete runs and the re-read proves the window is clean.daily schedule, which always runs in safe mode.io.kestra.plugin.core.flow.Loop
over a list, with concurrencyLimit: 1 on the part that deletes.Pause with a Slack approval. Or drop it for a collection you are sure about.