Scenario 01 — Protect a stateful app, consistently¶
The everyday case. You run a database on a PVC. You want nightly backups that the database can actually be restored from, and a sane retention window. This is the usual reason to install Kopiur, and it takes 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 application-consistent, not just crash-consistent.
What you'll deploy
Four objects, all in the app's namespace, because that's where the mover Job runs:
- a
Secretholding the backend credentials and the repository password; - a
Repository, a new S3 repository for this app; - a
SnapshotPolicy, the recipe: source PVC, copy method, hooks, retention; - a
SnapshotSchedule, the nightly cron.
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 and unquiesce your app. Swap the Postgres ones for your database's equivalents. |
retention.keep* |
SnapshotPolicy |
The GFS window: how many daily, weekly, and 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 is the default. It needs the CSI snapshot stack and is the right choice for a database like this, and it already gives you a crash-consistent point-in-time copy.
The beforeSnapshot and afterSnapshot hooks add application consistency on top. 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 before you trust the schedule. That confirms the whole chain: recipe, then mover, then snapshot. This Snapshot manifest ships with the example, in 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.