List icon
Return icon
Switch icon
Log icon
SlackIncomingWebhook icon
Schedule icon

DigitalOcean Stale Snapshot Cost Sentinel

List DigitalOcean snapshots on a schedule and alert Slack naming the real stale ones still billing by the GB.

Categories
CloudInfrastructure

A droplet or volume snapshot taken before a risky change, a migration, or just as a habit does not expire on its own, DigitalOcean keeps billing for its full size every month until someone deletes it. Nothing in the control panel flags a snapshot as forgotten, it looks identical to one taken yesterday for a reason. This blueprint lists every snapshot on the account on a schedule, finds the real ones older than your own retention threshold, and alerts Slack naming them and their real billable size, never an invented number.

How it works

  1. probe_snapshots (io.kestra.plugin.digitalocean.snapshot.List) lists every droplet and volume snapshot on the account, with allowFailure: true so a bad or expired API token does not stop the flow before it can be classified as PROBE_FAILED below.
  2. classify_snapshot_status (io.kestra.plugin.core.debug.Return) computes each snapshot's real age from its created_at timestamp with fromdateiso8601, and returns PROBE_FAILED when the probe did not return at all, CRITICAL or WARNING from the real count older than stale_age_days_threshold, against the two thresholds, HEALTHY otherwise.
  3. route_by_snapshot_status (io.kestra.plugin.core.flow.Switch) branches on that status: HEALTHY logs it, the other three alert Slack, WARNING and CRITICAL name the real stale snapshots with their real billable size in GB, never an invented list. A defaults branch logs anything unexpected.
  4. log_audit_summary always runs last. The errors block posts to Slack if the flow itself fails outside the gate logic above.

What you get

  • A real list of exactly which snapshots are older than your own policy, not a guess from the monthly bill after the fact.
  • A Slack alert naming the real snapshot and its real billable size, never an invented number.
  • A tiered severity you control, separate from DigitalOcean's own lack of any retention concept for snapshots.

Who it's for

  • Platform and FinOps teams running infrastructure on DigitalOcean who want snapshot sprawl caught on a schedule, not discovered during a cost review.
  • Teams evaluating the DigitalOcean plugin who want a working example of snapshot.List across both droplet and volume snapshots in one call.

Why orchestrate this with Kestra

DigitalOcean has no retention policy or alert for an aging snapshot, deleting one is always a manual, deliberate action. Kestra adds the schedule, the age threshold, and the Slack alert naming the real snapshots as one declarative, auditable flow, instead of a manual sweep someone has to remember to run.

Prerequisites

  • A DigitalOcean account and a personal access token with read access, generated under API in the DigitalOcean control panel. DigitalOcean's API has no official local emulator, this gate runs against a real account; a free-tier account and token are sufficient to call this endpoint.
  • A Slack incoming webhook for alerts.

Secrets

  • DIGITALOCEAN_TOKEN: personal access token used by the probe task.

  • SLACK_WEBHOOK: Slack incoming webhook URL used by the alert tasks and the errors block.

    In Kestra OSS, set these as base64-encoded environment variables, for example SECRET_DIGITALOCEAN_TOKEN, SECRET_SLACK_WEBHOOK.

Inputs

  • stale_age_days_threshold (INT, default 90): Snapshot age in days at or above which it is classified stale.
  • stale_count_warning_threshold (INT, default 1): Number of stale snapshots triggering a WARNING classification.
  • stale_count_critical_threshold (INT, default 5): Number of stale snapshots triggering a CRITICAL classification.

Outputs

  • {{ outputs.probe_snapshots.total }}: Total number of droplet and volume snapshots discovered across all pages.
  • {{ outputs.probe_snapshots.rows }}: List of snapshot maps containing id, name, created_at, size_gigabytes, and resource_type.
  • {{ outputs.classify_snapshot_status.value }}: The resulting staleness classification (HEALTHY, WARNING, CRITICAL, or PROBE_FAILED).

Quick start

  1. Generate a DigitalOcean API token and add the two secrets above.
  2. Take a snapshot of an existing droplet or volume, or use one you already have.
  3. Lower stale_age_days_threshold to a value below that snapshot's real age, run this flow manually, it should report WARNING naming that exact snapshot.
  4. Raise the threshold back above its real age, run again, it should report HEALTHY.
  5. Enable the every_monday trigger.

How to extend

  • Add a safe remediation branch that deletes only a snapshot whose name matches an explicit, operator-supplied list, gated behind an auto_remediate and dry_run pair, never a bulk delete of everything found stale.
  • Sum size_gigabytes across the stale snapshots and multiply by DigitalOcean's current per-GB snapshot storage rate to put a real monthly cost figure in the Slack alert.
  • Cross-reference resource_id against a current list of droplet or volume IDs to flag a snapshot whose source resource was already deleted, the snapshot it came from no longer exists to restore back onto.

Pitfalls

  • This gate only reads and alerts, it never deletes a snapshot automatically, a snapshot kept past its obvious use is sometimes a deliberate compliance or disaster-recovery decision that needs a human to confirm before anything is removed.
  • size_gigabytes is the snapshot's own billable size, not the size of the droplet or volume it was taken from, a mostly-empty disk can still produce a small, cheap snapshot.
  • DigitalOcean's API returns up to 200 snapshots per page; probe_snapshots follows pagination automatically, but an account with a very large number of snapshots will make more than one API call per run.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.