Scenario 01 — Protect a stateful app, consistently¶
The everyday case. You run a database on a PVC and want nightly, application-consistent backups with a sane retention window. This is the canonical reason to install Kopiur, and it's four resources in one namespace.
It goes one step past example 01: it adds hooks that quiesce the database around the snapshot, so the captured bytes are not just crash-consistent but application-consistent.
What you'll deploy
A Secret (backend creds + repo password), a Repository (a new S3 repo for
this app), a SnapshotPolicy (the recipe — source PVC, copy method, hooks,
retention), and a SnapshotSchedule (the nightly cron). All in the app's
namespace, because that's where the mover Job runs.
The values you'll actually change¶
| Field | Where | What it does |
|---|---|---|
backend.s3.bucket / prefix / endpoint / region |
Repository |
Points at your object store. |
KOPIA_PASSWORD |
the Secret |
Encrypts the repository. Lose it, lose the backups. |
sources[].pvc.name |
SnapshotPolicy |
The PVC to back up. |
hooks.*.workloadExec |
SnapshotPolicy |
The commands that quiesce/unquiesce your app. Swap the Postgres ones for your database's equivalents. |
retention.keep* |
SnapshotPolicy |
The GFS window — how many daily/weekly/monthly snapshots to keep. |
schedule.cron / jitter |
SnapshotSchedule |
When it runs. H picks a stable minute for you. |
Why hooks instead of just a snapshot?
copyMethod: Snapshot (the default; needs the CSI snapshot stack and is the right
choice for a database like this) already gives you a
crash-consistent point-in-time copy. The beforeSnapshot/afterSnapshot hooks add
application consistency: they run inside the workload to tell the database a backup
is starting, so what's on disk at snapshot time is a clean, restorable state. A hook
failure aborts the backup unless you set continueOnFailure: true (we set it on
the after hook so the database never gets stuck in backup mode).
apiVersion: v1
kind: Secret
metadata:
name: postgres-repo-creds
namespace: billing
type: Opaque
stringData:
AWS_ACCESS_KEY_ID: "REPLACE_ME"
AWS_SECRET_ACCESS_KEY: "REPLACE_ME"
# Lose this and the backups are unrecoverable — kopia cannot decrypt without it.
KOPIA_PASSWORD: "choose-something-long-and-random"
---
apiVersion: kopiur.home-operations.com/v1alpha1
kind: Repository
metadata:
name: postgres-primary
namespace: billing
spec:
backend:
s3:
bucket: my-backups
prefix: billing/postgres/
endpoint: s3.us-east-1.amazonaws.com
region: us-east-1
auth:
secretRef:
name: postgres-repo-creds
encryption:
passwordSecretRef:
name: postgres-repo-creds
key: KOPIA_PASSWORD
create:
enabled: true # this is a NEW repo — initialize it on first connect
---
apiVersion: kopiur.home-operations.com/v1alpha1
kind: SnapshotPolicy
metadata:
name: postgres-data
namespace: billing
spec:
repository:
name: postgres-primary # kind defaults to Repository (same namespace)
sources:
- pvc:
name: postgres-data
# ── How the volume is captured ────────────────────────────────────────────
# Snapshot takes a point-in-time CSI VolumeSnapshot, then kopia reads THAT — no
# app downtime, consistent on disk. Combined with the hooks below you also get
# application consistency. (copyMethod defaults to Snapshot — shown explicitly
# for teaching — and needs the CSI snapshot stack + a VolumeSnapshotClass for
# your driver, ideal for databases like this; Direct reads the live volume and
# works on any storage, but set it explicitly if you have no CSI snapshots.)
copyMethod: Snapshot
# volumeSnapshotClassName: csi-hostpath-snapclass # set if not the cluster default
# ── Quiesce the database around the snapshot ──────────────────────────────
# Hooks run IN THE WORKLOAD (not the mover): tell Postgres a backup is starting
# before the snapshot, and that it finished after. A hook failure aborts the
# backup unless continueOnFailure: true.
hooks:
beforeSnapshot:
- workloadExec:
podSelector:
matchLabels:
app: postgres
container: postgres
command: ["/bin/sh", "-c", 'psql -U postgres -c "SELECT pg_backup_start(''kopiur'');"']
timeout: 2m
afterSnapshot:
- workloadExec:
podSelector:
matchLabels:
app: postgres
container: postgres
command: ["/bin/sh", "-c", 'psql -U postgres -c "SELECT pg_backup_stop();"']
# Run even if something upstream failed, so the DB never stays in
# backup mode.
continueOnFailure: true
# ── How long to keep snapshots (GFS — the only successful-retention driver) ──
retention:
keepDaily: 14
keepWeekly: 6
keepMonthly: 3
---
apiVersion: kopiur.home-operations.com/v1alpha1
kind: SnapshotSchedule
metadata:
name: postgres-data-nightly
namespace: billing
spec:
policyRef:
name: postgres-data
schedule:
# Jenkins-style H pins a deterministic per-schedule minute; jitter spreads
# load so many schedules don't stampede the repository at once.
cron: "H 2 * * *"
jitter: 30m
runOnCreate: false # GitOps-friendly: don't fire the instant it's applied
Verify it worked¶
The Repository should reach Ready first:
Then fire one backup by hand to confirm the whole chain (recipe → mover →
snapshot) before trusting the schedule. This Snapshot manifest ships with the
example (its manual-snapshot section):
# Verification: fire ONE backup by hand to confirm the whole chain (recipe →
# mover → snapshot) before trusting the nightly schedule. generateName lets you
# re-apply this repeatedly to take another test snapshot.
apiVersion: kopiur.home-operations.com/v1alpha1
kind: Snapshot
metadata:
generateName: postgres-data-test-
namespace: billing
spec:
policyRef:
name: postgres-data
$ kubectl apply -f deploy/examples/scenarios/01-protect-stateful-app.yaml
$ kubectl get snapshots -n billing -w
NAME PHASE ORIGIN SNAPSHOT AGE
postgres-data-test-x9f Running manual 7s
postgres-data-test-x9f Succeeded manual k1f1ec0a8 44s
A SNAPSHOT id on a Succeeded backup means the data is in the repository. From
here the SnapshotSchedule takes over nightly.
See also¶
- Backups & schedules — every field on these three resources, including the other hook forms (
runJob,httpRequest) andcopyMethod. - Scenario 06 — verification drills — prove these backups actually restore.
- Movers, RBAC & credentials — where the mover runs and what it needs.