Scenario 07 — Point-in-time rollback¶
You need a specific moment, not "yesterday." A bad deploy at 14:30 quietly
corrupted data over the next hour. You want the volume exactly as it was at 14:00
— just before things went wrong — and you don't want to scroll through Snapshot CRs
guessing which one that was.
This is what source.fromPolicy with asOf is for: it resolves through the
SnapshotPolicy's identity (so it works even if the relevant Snapshot CR has aged
out of the catalog) and picks the newest snapshot at or before an instant. As
always, restore into a side-by-side PVC and verify before cutting over.
Step 1 — Choose the instant¶
You don't list snapshots — you name the time. asOf takes an RFC3339 timestamp and
resolves to the newest snapshot at or before it. (Prefer counting backwards? Use
offset: 0 = latest, 1 = previous, and so on.)
asOf vs offset
Use asOf when you know when things were good ("just before the 14:30 deploy").
Use offset when you know how many snapshots back ("the one before last"). Set
one, not both.
Step 2 — Restore into a clone and verify¶
The bundle restores into a fresh postgres-data-1400 PVC; the live volume is
untouched. Because fromPolicy defaults to onMissingSnapshot: Continue
(deploy-or-restore), a deliberate rollback sets it to Fail so an instant with no
snapshot is a loud error, not a silent empty volume.
# Scenario 07 — Point-in-time rollback (roll an app back to a known-good moment)
#
# A bad deploy at 14:30 corrupted the data over the next hour. You don't want
# "yesterday" and you don't want to hunt for the exact Snapshot CR — you want
# "whatever the data looked like at 14:00, just before it went wrong."
#
# `source.fromPolicy` resolves through the SnapshotPolicy's IDENTITY (not a specific
# Snapshot CR), so it works even if that Backup has aged out of the catalog. `asOf`
# picks the newest snapshot AT OR BEFORE an instant; `offset` picks by position
# (0 = latest, 1 = previous, ...). Restore into a NEW PVC and compare before cutting
# over — never overwrite the live volume on the first attempt.
#
# Field shapes verified against crates/api: source.fromPolicy =
# { name, namespace?, asOf?, offset? }; target.pvc creates a new PVC.
---
apiVersion: kopiur.home-operations.com/v1alpha1
kind: Restore
metadata:
name: postgres-rollback-1400
namespace: billing
spec:
source:
fromPolicy:
name: postgres-data # the SnapshotPolicy protecting the live volume
asOf: 2026-05-20T14:00:00Z # the last good moment, just before the bad deploy
# offset: 1 # ...or by position instead of a timestamp
target:
pvc:
name: postgres-data-1400 # a side-by-side clone — the live PVC is untouched
storageClassName: fast-ssd
capacity: 100Gi
accessModes:
- ReadWriteOnce
policy:
# fromPolicy DEFAULTS to Continue (deploy-or-restore). For a deliberate rollback
# you want it to FAIL loudly if nothing matches that instant.
onMissingSnapshot: Fail
waitTimeout: 5m
#
# Watch it, then verify against the clone before you cut over:
# kubectl get restore postgres-rollback-1400 -n billing -w
# # spin a throwaway psql pointed at postgres-data-1400, confirm the data is right
#
# Cut over once you're satisfied: scale the app down, then either repoint it at the
# clone PVC, or do an in-place mirror restore into the live PVC (example 15):
#
# spec:
# source: { fromPolicy: { name: postgres-data, asOf: 2026-05-20T14:00:00Z } }
# target: { pvcRef: { name: postgres-data } } # the EXISTING live PVC
# options: { enableFileDeletion: true } # exact mirror of the snapshot
$ kubectl get restore postgres-rollback-1400 -n billing -w
NAME PHASE AGE
postgres-rollback-1400 Resolving 2s
postgres-rollback-1400 Restoring 9s
postgres-rollback-1400 Completed 44s
Point a throwaway client at the clone PVC and confirm the data is the moment you wanted.
Step 3 — Cut over¶
Once you trust the clone, scale the app down and either repoint it at the clone, or do
an in-place mirror restore into the live PVC — target.pvcRef +
options.enableFileDeletion: true to make the live volume an exact mirror of the
chosen instant (see example 15).
The in-place form is shown commented at the bottom of the bundle.
Never roll back in place on the first try
Restoring over a mounted, running database is a corruption hazard, and if you pick the wrong instant the original is gone too. Clone, verify, then cut over.
See also¶
- Restores → point-in-time — the
asOf/offsetreference. - Example 14 — point-in-time / offset restore and example 15 — in-place mirror.
- Scenario 02 — recover lost data — the same safe clone-and-verify habit for a known
SnapshotCR.