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 Tenax

helm install tenax oci://registry.example.com/helm/tenax --version 0.1.0 \
  -n tenax-system --create-namespace \
  --set image.repository=registry.example.com/tenax/tenax --set image.tag=v0.1.0 \
  --set 'targetNamespaces={team-a}' \
  --set examplePolicies.enabled=true --set examplePolicies.store=main

targetNamespaces lists the namespaces whose databases Tenax backs up; Tenax gets a Role there to read the tenax-* 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 tenax-backups.key: without it no backup can be restored.

age-keygen -o tenax-backups.key        # prints "Public key: age1…"
kubectl -n tenax-system create secret generic tenax-age-identity \
  --from-literal=identity="$(grep AGE-SECRET-KEY tenax-backups.key)"
kubectl -n tenax-system create secret generic tenax-s3 \
  --from-literal=accessKeyId=AKIA… --from-literal=secretAccessKey=…
store.yaml
apiVersion: tenax.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: tenax-system, name: tenax-s3 }
  encryption:
    ageRecipients: [age1qxyz…]                        # the public key from age-keygen
    identitySecret: { namespace: tenax-system, name: tenax-age-identity }

kubectl apply -f store.yaml, then kubectl get backupstores: main turns Ready once Tenax reaches the bucket and the keys parse.

3. The database user

Tenax connects with one user, tenax. Create it with the SQL in Database users, then store its password next to the database:

kubectl -n team-a create secret generic tenax-postgres --from-literal=username=tenax --from-literal=password=…

For restore to any second, also let it replicate: add host replication tenax all scram-sha-256 to pg_hba.conf and reload. You can do this later. Until then Tenax takes logical backups and its Replication condition says what to add.

4. Declare the target

target.yaml
apiVersion: tenax.republic.global/v1alpha1
kind: BackupTarget
metadata:
  name: app
  namespace: team-a
  labels:
    tenax.republic.global/org: acme
    tenax.republic.global/project: shop
    tenax.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: tenax-postgres }
  checks:
    - name: orders-not-empty
      database: app
      sql: select count(*) > 0 from orders

Within a minute Tenax starts a streamer for the target and takes the first full backup on its own (reason chain-restart). If the user may not replicate yet, the target shows Replication=False with the fix instead, and backups are logical until you apply it:

kubectl -n team-a get backuptargets,backups

5. Restore, on purpose

tenax login --server https://tenax.example.com     # or TENAX_SERVER + port-forward, see Console, CLI and access
tenax targets
tenax restore team-a/app --at "$(date -u -d '-10 min' +%FT%TZ)" --wait
tenax 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: