Skip to main content
RunBook Academy

← All checklists in Docker & Containers

QuarterlyDisaster recovery

Checklist: Disaster recovery drill

26 items ·22 critical ·4 warn ·0 info

What this is

A drill you run, on a clock, with two numbers agreed in advance and a written PASS or FAIL at the end. It is not a document review, a tabletop walkthrough, or a restore performed by the person who wrote the backup job on the host they wrote it for.

Every item on this list resolves to yes or no with something measured behind it. Three of them produce numbers that go in the record whether they pass or fail: time to first byte of backup read, total RTO, and measured RPO. Those numbers are the output of the drill. The tick boxes are how you get them.

Before the clock starts

Agree and write down:

RTO_MINUTES=60            # declaration to verified-serving
RPO_MINUTES=15            # acceptable data loss
PROD_HOST=prod-app-01.example.com
DR_HOST=198.51.100.40     # a DIFFERENT host
STACK=/srv/stacks/api
KNOWN_RECORD_ID=REPLACE_ME   # a record that exists before the drill

Pick KNOWN_RECORD_ID before you start and note what it should contain. Verifying against a record you look up after the restore means verifying against whatever came back.

Running it

Work top to bottom and do not skip ahead when something blocks; the block is the finding. If backup-located-unaided takes fifty minutes, the drill has already told you the most useful thing it is going to tell you, and the correct response is to record fifty minutes and continue rather than to phone the person who knows.

Resist two temptations. The first is to fix things quietly as you hit them — fix them, but write down that you had to. The second is to stop at stack-started. Containers running is the point at which most drills are declared successful and the point at which the least has actually been proven.

Scoring

The drill is PASS only if every critical item is yes AND the measured RTO is within target AND the measured RPO is within target. Anything else is FAIL, and a FAIL is a normal, useful outcome for a first drill — it is the reason to run the second one.

Do not soften a FAIL into a pass with caveats. The value of the record is that a run of numbers across quarters is comparable, and one generous quarter breaks the series.

  • Drill date: _________________ Result: PASS / FAIL
  • Restored onto host: _________________
  • Time to locate backup: ______ min
  • Measured RTO: ______ min (target ______)
  • Measured RPO: ______ min (target ______)
  • Data loss: ______ records
  • Run by: _________________ Observer: _________________

Critical22 items

  1. hostname -f   # run on the restore target; the output must not be "$PROD_HOST"
  2. docker version --format 'engine={{.Server.Version}}'; cat /etc/docker/daemon.json 2>/dev/null
  3. date -Is | tee /var/tmp/dr-drill-start
  4. docker compose -f "$STACK/compose.yaml" pull
  5. docker compose -f "$STACK/compose.yaml" config -q
  6. docker run --rm -v api_data:/dbdata -v "$PWD":/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"; docker run --rm -v api_data:/d alpine sh -c 'du -sh /d; ls -lt /d | head'
  7. docker run --rm -v api_data:/d alpine sh -c 'ls -lan /d | head'
  8. curl -sS --fail "https://$DR_HOST/api/health"; curl -sS --fail "https://$DR_HOST/api/orders/$KNOWN_RECORD_ID"
  9. echo "start=$(cat /var/tmp/dr-drill-start) end=$(date -Is)"

Warning4 items

  1. docker compose -f "$STACK/compose.yaml" up -d; docker compose -f "$STACK/compose.yaml" ps
  2. docker ps --format '{{.Names}} {{.Ports}}' | grep -E '0\.0\.0\.0:|\[::\]:'