Skip to content

Build master @ bc50cd5bunreleased — 31 commits past v1.282.0Last reviewed Last changed Grade fresh (aging >60d, stale >120d)Source / History

Runbook: Project Data Restore ​

Purpose: Recover a BoB-managed project's Cloudflare D1 data after a bad migration, a bad deploy, a deleted row or a lost database — choosing the path with the least data lost (#3085, cap #3067) When to use: Rows in a project's PROD database are wrong or gone; a migration went badly; the database itself is gone; or you are rehearsing a restore into TEST Prerequisites: The project's dev.json declares environments.prod.backup and its backup Worker is deployed (bob backup setup --apply, then the two next: lines it prints); wrangler authenticated to the project's Cloudflare account (wrangler whoami); a terminal you are sitting at — a PROD restore never runs unattended Estimated time: 5–15 minutes for Time Travel; the export path's time is what bob backup restore prints as its measured RTO

This runbook is about a project's data. Rebuilding BoB itself (BOB_HOME and a project checkout) is the other drill: Restore Drill.

RPO and RTO — which path loses less ​

PathRPO (how much data you can lose)RTO (how long it takes)Reaches back
1. D1 Time Travel (wrangler d1 time-travel restore)about a minute — any point in timeminutes, in place30 days (Workers Paid), 7 days (Free)
2. Nightly export (bob backup restore)up to one schedule interval — a day with the default 0 3 * * *measured: bob backup restore prints itthe declared retention: 7 dailies, 12 monthlies by default

Time Travel loses less and is faster, so it comes first. The export is for what Time Travel cannot reach: a point older than its window, a database that was deleted, or a restore into another database (TEST) to inspect before touching PROD.

Steps ​

1. Stop the damage ​

Stop whatever is still writing bad data: pause the deploy, disable the cron, or put the site into maintenance. Note the time the damage began, in UTC — every later step asks for it.

bash
date -u +%Y-%m-%dT%H:%M:%SZ

2. Take a backup of the damaged state ​

Keep what is there now, so the restore itself can be undone:

bash
bob backup run

Expected: landed <db>/<stamp>.sql … after …. Exit 1 means the Worker did not land a dump in time; carry on — Time Travel does not need it.

3. Path 1 — D1 Time Travel (recent mistake, database still exists) ​

Find the bookmark for a moment just before the damage began:

bash
wrangler d1 time-travel info <database_name> --timestamp 2026-09-29T09:14:00Z

It prints the bookmark for that moment. Then restore the database in place to that bookmark:

bash
wrangler d1 time-travel restore <database_name> --bookmark <bookmark>

wrangler asks you to confirm, then reports the restore along with the bookmark the database was at before it. Keep that bookmark: restoring to it undoes this restore. Go to step 5.

If the timestamp is outside the Time Travel window, or the database was deleted, use path 2.

4. Path 2 — the nightly export (bob backup restore) ​

Check which dumps you have and how old the newest is:

bash
bob backup status

Restore into TEST first and verify it there. With no --from, the newest dump is used; name an older one to reach back before the damage:

bash
bob backup restore                                          # the newest dump → TEST
bob backup restore --from <db>/2026-09-28T0300Z.sql         # a named dump → TEST

A deploy that failed after its migration applied. bob deploy (and the promote workflow in CI) takes a backup immediately before it applies D1 migrations, when the env declares a backup and migrations are pending. The deploy output names it on one line:

text
pre-migrate backup: r2://<bucket>/<database>/<stamp>.sql

That dump is the schema and data from just before the migration. Pass the r2:// key as it is printed:

bash
bob backup restore --from r2://<bucket>/<database>/<stamp>.sql   # → TEST first, as above

Expected: one ok line per table (users: 2 restored, 2 in the sidecar), then restored and verified in … (…s, measured RTO). Exit 1 with DIFF lines means the row counts did not match — do not take that dump to PROD.

Check the TEST app against it. When it is right, restore the same dump into PROD. This drops PROD's tables and replaces them, so it is refused unless a human passes its gate: automation Level 1, or the explicit flag, and then the database name typed at the terminal:

bash
bob backup restore --env prod --from <db>/2026-09-28T0300Z.sql --i-understand-this-overwrites-prod

Expected: the prompt This overwrites the PROD database <database_name>. Type its name to go on:, then the same ok lines and the measured RTO. Exit 4 means the gate refused and nothing was touched.

For a project whose Cloudflare account an agent may not act in (Client A's), the operator runs this step; an agent files a todo with the exact command.

5. Verify and record ​

  1. Load the app against PROD and check the rows that were damaged.
  2. Run bob backup status — it should be FRESH.
  3. Comment on the incident issue (or the drill's run issue) with the path taken, the timestamp or dump restored, the measured RTO, and how much data was lost (the RPO you actually got).