Skip to content

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=…
store.yaml
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

target.yaml
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):

kubectl -n team-a get backuptargets,backups

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: