Skip to main content
Backups are only useful if they are still there when you need them. DBDock applies several layers of protection so that retention, cleanup, and manual deletes can never remove the wrong object — and so that a delete can be undone.

What is protected

Every delete that DBDock performs against object storage (S3, Cloudflare R2, Cloudinary, or local) passes through a guard before anything is removed. This covers automatic retention, WAL cleanup, and the dbdock delete / dbdock cleanup commands, on both DBDock-managed storage and your own buckets.

Layer 1 — Delete guard

Before any object is deleted, the key is validated and checked against an allow-list of DBDock-owned prefixes:
  • Structural checks — empty keys, surrounding whitespace, leading slashes, folder-style keys (.../), path traversal (..), and wildcards are refused.
  • Prefix allow-list — keys must live under a DBDock prefix (backups/, wal/, dbdock_backups/, or backup-). Anything outside the namespace DBDock created is never a deletion target.
  • Circuit breaker — a single run cannot delete more than maxDeletesPerWindow objects within a rolling window (default 1000 per minute), so a bug in a retention policy can’t cascade into wiping a bucket.
If a check fails, the delete is refused and the object is left untouched.

Layer 2 — Recycle bin

Deletes are soft by default. Instead of being removed immediately, an object is copied to a .trash/<timestamp>/ prefix (a fast server-side copy on S3/R2) and only then removed from its original location. A small .trashmeta.json sidecar records the original key, the time, and the reason. Trashed objects are hard-purged automatically once they are older than trashRetentionDays (default 14 days), and can be restored before then.
const storage = app.get(StorageService);
const restoredKey = await storage.restoreFromTrash(
  '.trash/2026-06-19T22-31-00-000Z/backups/db/2026-06-19/abc.sql.gz',
);
The recycle bin lives inside the same bucket. For protection against bucket-level mistakes (a bad lifecycle rule, a compromised key, an accidental bucket action), enable object versioning on the bucket itself so deletes become recoverable delete-markers.
1

Enable versioning

In the Cloudflare dashboard open R2 → your bucket → Settings → Object versioning and turn it on. On AWS S3, enable Bucket Versioning under the bucket’s Properties.
2

Add a lifecycle rule

Add a lifecycle rule to expire non-current versions after a retention window (for example 30 days) so old versions don’t accumulate cost.
3

Use a least-privilege key

Give DBDock an API token scoped to a single bucket with only the object read/write/delete permissions it needs — not account-wide access.

Configuration

All of layer 1 and layer 2 are on by default. Tune them under storage.deletionSafety:
{
  "storage": {
    "provider": "r2",
    "deletionSafety": {
      "enabled": true,
      "recycleBin": true,
      "trashRetentionDays": 14,
      "maxDeletesPerWindow": 1000
    }
  }
}
FieldDefaultDescription
enabledtrueMaster switch for the guard and allow-list.
recycleBintrueSoft-delete to .trash/ instead of deleting immediately.
trashRetentionDays14Days a trashed object is kept before it is purged.
maxDeletesPerWindow1000Circuit-breaker limit per rolling minute.
allowedPrefixesDBDock rootsOverride the protected prefix allow-list.