Quickstart¶
This takes a Postgres database that already runs in your cluster from no backups to a tested restore in about ten
minutes. You need kubectl and helm access to the cluster, an S3-compatible bucket with credentials, and
age on your machine.
No cluster at hand?
Try the console and the CLI against a demo server first: one command, no Kubernetes, no database.
1. Install Keeper¶
helm install keeper oci://registry.example.com/helm/keeper --version 0.1.0 \
-n keeper-system --create-namespace \
--set image.repository=registry.example.com/keeper/keeper --set image.tag=v0.1.0 \
--set 'targetNamespaces={team-a}' \
--set examplePolicies.enabled=true --set examplePolicies.store=main
targetNamespaces lists the namespaces whose databases Keeper backs up; Keeper gets a Role there to read the
keeper-* credential secrets and nothing else. examplePolicies creates the shared prod, dev and critical
policies. Every value is described in Helm values.
2. Keys and the bucket¶
Generate the key pair that encrypts every backup. Keep an offline copy of keeper-backups.key: without it no
backup can be restored.
age-keygen -o keeper-backups.key # prints "Public key: age1…"
kubectl -n keeper-system create secret generic keeper-age-identity \
--from-literal=identity="$(grep AGE-SECRET-KEY keeper-backups.key)"
kubectl -n keeper-system create secret generic keeper-s3 \
--from-literal=accessKeyId=AKIA… --from-literal=secretAccessKey=…
apiVersion: keeper.republic.global/v1alpha1
kind: BackupStore
metadata:
name: main
spec:
s3:
endpoint: https://s3.us-east-1.amazonaws.com
region: us-east-1
bucket: db-backups
credentialsSecret: { namespace: keeper-system, name: keeper-s3 }
encryption:
ageRecipients: [age1qxyz…] # the public key from age-keygen
identitySecret: { namespace: keeper-system, name: keeper-age-identity }
kubectl apply -f store.yaml, then kubectl get backupstores: main turns Ready once Keeper reaches the bucket
and the keys parse.
3. The database users¶
Keeper connects with two least-privilege users: a read-only one for dumps and inventories, and a replication one for the change stream. Create them with the SQL in Database users, then store their passwords next to the database:
kubectl -n team-a create secret generic keeper-backup-postgres --from-literal=username=keeper_backup --from-literal=password=…
kubectl -n team-a create secret generic keeper-repl-postgres --from-literal=username=keeper_repl --from-literal=password=…
4. Declare the target¶
apiVersion: keeper.republic.global/v1alpha1
kind: BackupTarget
metadata:
name: app
namespace: team-a
labels:
keeper.republic.global/org: acme
keeper.republic.global/project: shop
keeper.republic.global/env: prod
spec:
engine: postgres
endpoint: { host: postgres, port: 5432 }
databases: [app]
policy: prod # nightly fulls, 7-day point-in-time window
credentials:
secretRef: { name: keeper-backup-postgres }
replicationSecretRef: { name: keeper-repl-postgres }
checks:
- name: orders-not-empty
database: app
sql: select count(*) > 0 from orders
Within a minute Keeper starts a streamer for the target and takes the first full backup on its own
(reason chain-restart):
5. Restore, on purpose¶
keeper login --server https://keeper.example.com # or KEEPER_SERVER + port-forward, see Console, CLI and access
keeper targets
keeper restore team-a/app --at "$(date -u -d '-10 min' +%FT%TZ)" --wait
keeper query <sandbox> -e "select count(*) from orders"
The restore runs your checks before it reports success. The sandbox deletes itself when its TTL expires. Next:
- Backups and the restore window explains what the window is and what keeps it unbroken.
- Verification turns this manual drill into a weekly, alerting one.
- Monitoring wires the alerts and the dashboard.