Backup and restore
Backup and restore
Two things in the namespace hold state you cannot rebuild from the chart: your database, and the platform Secret. Everything else is rendered by helm install and can be recreated at any time.
The platform Secret
The chart names it <release>-platform-env, so for a release called my-mvno it is my-mvno-platform-env. It carries the values the chart generated (JWT_SECRET, the age KEK, the Postgres and Redis passwords, the session password) and the six keys the license agent writes into it:
| Key | What it is |
|---|---|
SIGNING_SEED | The deployment's private signing key, generated inside your cluster and never sent anywhere |
POP_SIGNING_SEED | The current proof-of-possession key, rotated on every renewal |
PARENT_LEASE_TOKEN | The license the platform is currently serving on |
PARENT_BASE_URL | The parent this deployment is pinned to |
REQUIRE_PARENT_LEASE | true once enrollment succeeds, which is what makes the API fail closed |
ACTIVATION_CODE_CONSUMED | The activation code already spent, so it is never replayed |
A deployment the Hub has revoked also carries LEASE_STATUS, cleared again once it recovers.
Back it up:
kubectl -n my-mvno get secret my-mvno-platform-env -o yaml > my-mvno-platform-env.backup.yaml
That file is as sensitive as the cluster itself. It contains the signing key, the database password and the JWT secret in base64, which is encoding, not encryption. Store it wherever you keep your other production keys, encrypted at rest.
Restoring it is a plain apply:
kubectl -n my-mvno apply -f my-mvno-platform-env.backup.yaml
kubectl -n my-mvno rollout restart deploy/my-mvno-platform-license-agent
If you already run SOPS, sealed-secrets or the External Secrets Operator, point secrets.existingSecret at your own Secret and your existing backup process covers this automatically. One caveat: the license agent patches keys into that Secret on every renewal, so do not enable a sync policy that prunes keys your secret store did not create. See Secrets.
The database
postgres.mode=operator is the default, which means CloudNativePG owns the Postgres Cluster, and CNPG has real backup primitives instead of a shell script wrapping pg_dump. Point it at object storage and let it take backups on a schedule:
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: my-mvno-platform-postgres-daily
namespace: my-mvno
spec:
schedule: "0 3 * * *"
backupOwnerReference: self
cluster:
name: my-mvno-platform-postgres
That assumes the Cluster already has a barmanObjectStore (or another CNPG-supported destination) configured, which is cluster-level object storage config you own and apply yourself; the chart does not set one for you. Once it is there, CNPG also gives you point-in-time recovery: restore to any point covered by your retention window, not just the moment of the last dump. A one-off backup outside the schedule is a Backup resource pointed at the same Cluster name.
postgres.mode=external? Backups belong to that managed Postgres and its own tooling (RDS snapshots, your provider's PITR, whatever it offers), not to this chart.
A database backup on its own is only half a backup. Take the platform Secret at the same time, because the two have to match: the database contains rows encrypted with keys that live in the Secret.
Losing the Secret
This is recoverable. It is not the end of your deployment, and you do not lose customer data.
The signing key and the current license are both gone, so the deployment can no longer prove it is who it says it is. The fix is to enroll again from scratch:
- Mint a fresh activation code from the Hub console (or from your parent MVNE's console, if that is what you activate against).
- Run
helm upgradewith the new code. The chart regenerates the Secret, and the license agent generates a new signing key inside your cluster and enrolls with it.
helm upgrade my-mvno oci://ghcr.io/solvegio/charts/platform \
--version <version> --namespace my-mvno -f my-values.yaml \
--set activation.code=<new-code>
Two things are worth knowing before you take that path.
The old code cannot be replayed. It was consumed and bound to the signing key you just lost, so an attempt to reuse it fails with enrollment: activation code already used by a different key. Always mint a new one.
In operator mode, the CNPG Cluster will not accept a regenerated app-role password on its own. The chart hands CNPG that password once, through bootstrap.initdb.secret, at cluster creation on an empty volume; it is not something CNPG re-reads later. If you keep the existing PersistentVolumeClaim and let the chart generate a brand-new password, the running database still expects the old one and the API will not connect. So restore the Secret from backup wherever you possibly can, and treat regeneration as the path of last resort. If you must regenerate against an existing volume, reset the password inside Postgres by hand (ALTER ROLE ... WITH PASSWORD ...) to match the new Secret. The supabase_auth_admin role is different: CNPG's managed.roles keeps that one's password reconciled against the <release>-platform-pg-auth-role Secret on every change, so it picks up a regenerated password without manual intervention. In external mode, the running database was never touched by the chart in the first place, so nothing here applies; the password mismatch, if any, is between you and your managed provider.