Back up and restore a deployment¶
A deployment's records sit on its cluster's volumes: people, access, plugin settings and their sealed secrets, instruments, the book, the street and the lake, in one database, beside the keys those secrets are sealed with. A reset of Rancher Desktop, a lost node or a deleted namespace loses them. A backup is one file that holds what a restore needs to bring the deployment back, sealed so that only you can open it.
Backups are made in an image of their own, ghcr.io/open-meridian/meridian-backup, which the
published chart pins to the image built with it. You need CLI 0.1.45.
| To | Run or do |
|---|---|
| Make the identity backups are sealed to | meridian backup key |
| Back up on a schedule | Set backup.recipient, backup.path and backup.schedule in the chart's values |
| Back up now | meridian backup |
| Back up now and copy the file here | meridian backup --to <dir> |
| Restore a backup | meridian restore <file> --identity <file> |
You back up and restore with your own cluster rights, through kubectl and, to restore, helm.
The backup itself runs in the cluster, as a Job that holds no rights there and changes nothing in
it. Nothing in the deployment restores itself.
What a backup holds¶
One file per backup, named <release>-<UTC time>.meridian-backup, for example
meridian-20261012T020000Z.meridian-backup. It is a tar, sealed with age to the public key in
backup.recipient. It holds:
- The keys and credentials the chart keeps: the settings key that plugins' secret settings are sealed with, the dashboard's signing key and its public keys, the database's passwords and URLs, the broker's credentials, and the Secrets first run wrote. Without the settings key a restored deployment could not read its secret settings, and without the dashboard's key no sidecar would trust the dashboard.
- Your own Secrets that the values name, such as an
existingSecret, a TLS Secret or a plugin's image pull Secret. - The deployment's key, without which it would have to be registered with the platform again.
- The values the release was installed with, without
deployment.enrolmentCode, which is spent once used. - The database, when it is included (see
backup.database): apg_dumpof the database first run made, with every table's row count read in the dump's own snapshot. - A manifest of what was taken, from which release, chart and app version, and every member's SHA-256, which restore checks.
It does not hold:
- The plugin registry. Plugins' images are uploaded again after a restore, which says which.
- Edge plugins' own storage. An edge plugin starts with empty storage after a restore, and gathers again what its vendor still serves. The book's and the street's rows are in the database.
- A database somebody else runs, unless you ask for it (
backup.database: true). That database is its provider's to back up.
The cluster cannot read its own backups. The private half of the key, the identity, is yours, and is needed only to restore. A copied backup volume reveals nothing without it.
Make the identity¶
It needs no deployment. It writes a new identity to meridian-backup-identity.txt in the current
directory, readable by you alone, and prints its public half:
Wrote the identity to meridian-backup-identity.txt, readable by you alone. Keep it off the cluster,
and where a reset of this machine does not reach: a backup opens with it and nothing else.
Its public half, for backup.recipient:
age1…
--to <file> writes it somewhere else. It never writes over a file that is already there, since a
backup sealed to the old identity would be lost with it.
Keep the identity off the cluster, and somewhere a reset of this machine does not reach, such as
your password manager or a vault. Every backup opens with it and with nothing else: lose it, and
the backups sealed to it cannot be restored. The public half, age1…, is not secret, and is safe
in your values and in Helm's release history.
Turn backups on¶
Backups are set in the chart's values, under backup. Nothing is made until backup.recipient and
a place to write are both set. Then the chart makes a CronJob, <release>-meridian-runtime-backup,
that writes one backup each time it runs.
backup:
recipient: age1… # the public half `meridian backup key` printed
path: /Users/<you>/meridian-backups # a directory on the node
schedule: "0 2 * * *" # daily at 02:00, in the cluster's time zone
keep: 14
| Value | Default | Meaning |
|---|---|---|
backup.recipient |
none | The age public key (age1…) every backup is sealed to. |
backup.path |
none | A directory on the node to write backups to. The chart makes a volume from it, which is kept when the release is uninstalled and never deleted by the deployment. |
backup.existingClaim |
none | A claim you made, on storage that outlives the cluster, in place of backup.path. On a cluster of several nodes, use this, on shared storage. |
backup.size |
20Gi |
The size of the volume made from backup.path, requested once. |
backup.schedule |
none | When to back up, in cron, in the cluster's time zone. With none, nothing is scheduled, and meridian backup still takes one when asked. |
backup.keep |
14 |
How many of this release's backups to keep. Once a new one is written, the oldest beyond this are removed. |
backup.database |
none | Whether the database is dumped. See below. |
backup.image |
ghcr.io/open-meridian/meridian-backup |
The image the backup runs in: pg_dump and psql at the major version of the database the chart brings, and age. |
Daily suits a deployment you depend on, hourly a development one. A restore brings the deployment back as it was at the last backup, so the interval is what you can lose.
Where backups go. On Rancher Desktop, backup.path must be under your Mac's home folder, which
the VM mounts and a reset of Rancher Desktop keeps. A path inside the VM is wiped with it.
Elsewhere, name storage that survives the cluster: a disk or a share mounted on the node, or a claim
(backup.existingClaim).
What is removed. Only this release's backups in that place count against backup.keep, and
only once a new one is written. A backup that fails removes nothing. Neither does one taken while
the deployment has no database URL, before first run or while a restore withholds it, so a restore
in progress never rotates out a backup that holds the database.
The chart refuses values it cannot honour, before anything is installed:
| It refuses | Because |
|---|---|
backup.schedule without backup.recipient |
There is no unencrypted backup. |
backup.schedule without backup.path or backup.existingClaim |
There is nowhere to write. |
Both backup.path and backup.existingClaim |
Name one place for backups. |
backup.database other than true, false or nothing |
Those are the three it takes. |
Which database is dumped¶
The keys, the values and the manifest are backed up whatever you choose. The database is dumped by this rule:
backup.database |
A database the deployment brought | A database you run |
|---|---|---|
| Not set (the default) | Dumped | Not dumped: it is its provider's to back up |
true |
Dumped | Dumped, as its migrating role |
false |
Not dumped | Not dumped |
To dump a database you run, backup.image must hold a pg_dump no older than its server: name an
image that has one. Restore does not load such a dump yet (see below).
Apply the values¶
At install, pass them to meridian up in a values file:
On a running deployment, give them to meridian upgrade-deployment, which merges
them over the deployment's own values, shows each path they set, never a value, and asks first; at
the version it already runs, that is an upgrade at that version:
From your own pipeline, apply them with Helm, as an upgrade to the version it already runs, keeping its own values (see Upgrade a deployment):
helm upgrade meridian oci://ghcr.io/open-meridian/charts/meridian-runtime \
--version <the version it runs> --namespace meridian --reset-then-reuse-values -f backup.yaml
Back up now¶
It runs the backup CronJob once, now, whether or not a schedule is set, waits for it, and says what it wrote. Only names, counts, sizes and the file's hash are printed, never a value:
Backed up meridian to meridian-20261012T143005Z.meridian-backup (<bytes> bytes), on the deployment's backup volume.
backup: secrets: meridian-meridian-runtime-settings-key …
backup: the deployment's key, from its volume
backup: the database meridian: <n> tables, <n> rows, dump <bytes> bytes
backup: wrote meridian-20261012T143005Z.meridian-backup <bytes> bytes sha256 <hash>
With --to <dir> it also copies the file to that directory on this machine, and checks its size
and SHA-256 against what the Job wrote:
Copied to /Users/<you>/meridian-backups/meridian-20261012T143005Z.meridian-backup, its SHA-256 checked. It opens only with the identity whose public half is backup.recipient.
Take one with --to before an upgrade, or before resetting Rancher Desktop.
If backups are not set up, it says so and names the values to set. --release and -n name another
release or namespace; --timeout (default 10m) is how long it waits. See
meridian backup.
Restore a backup¶
Restore checks everything before it changes anything, shows what it will do and asks Restore it
here?, refuses a namespace a deployment kept anything in, never installs a chart older than the
backup's, and loads a dump of a database you run itself.
You need the backup file, the identity it was sealed to, and a cluster to restore into, with
kubectl pointing at it. Restore goes only into a namespace holding no deployment, so it never
restores over one.
By default it restores into the namespace the backup was taken in, as the release it was taken of,
from the published chart at the version the backup was taken on. -n names another namespace.
The release must be the backup's own, because every object it restores is named for it.
It says each step on stderr as it goes:
- Open. It decrypts the file with the identity, checks every member against the backup's SHA-256 list, and reads the manifest. A file sealed to another identity, or a member that is not what was written, is refused.
- Check where. It names the cluster and namespace, and refuses, changing nothing, if the
namespace holds the release or anything a deployment keeps or would take up: the backup's
Secrets and ConfigMaps, the key's Secret, the claims the database the chart brings and its
registry would take, or a backup volume a deleted namespace left. One refusal lists them all.
It reads the chart at the version the backup was taken on, or
--chart-version, and refuses one older than the backup's; a newer one is said, the restore then being an upgrade. - Ask. It shows the backup, the cluster and namespace, the chart and version, what it will
make and what it will do with the database, and asks
Restore it here?.--yesanswers for a script; with no terminal and no--yes, nothing is changed. Then it makes the namespace if there is none. - Put the keys back. It makes every Secret the backup holds, with the database's URL left empty
so that nothing starts against the database yet. Where the deployment made its own key, the key
goes into a Secret,
<release>-meridian-runtime-key; where you gave it one (key.existingSecret), your own Secret is put back instead. - Install. It runs
helm installwith the values you chose, those that differ from the chart's own defaults, so a default the chart has changed since still applies. - Restore the database. It starts the database the deployment brings, makes the database and its two roles with the backed-up passwords, refuses a database that already holds tables, and loads the dump.
- Check it. It counts every table's rows and compares them with the counts the backup took. If any differs it stops, names each table, and keeps the URL withheld: nothing runs against a database that does not hold what was backed up.
- Start. Only then does it give the components the database's URL. The migration runs, the conductor and the dashboard restart with the URL, first run gives up its rights, and it waits for every component of the release to be ready.
Opened meridian-20261012T143005Z.meridian-backup: meridian (DEP-…) taken 20261012T143005Z of chart 0.1.…, every member's hash checked
Restoring into the cluster rancher-desktop, namespace meridian
Made <n> Secrets (the database's URL withheld), the deployment's key among them
Installed meridian from oci://ghcr.io/open-meridian/charts/meridian-runtime 0.1.…
Started the database, and made meridian and its two roles
Restored meridian: <n> tables and <n> rows, each table's count as the backup counted it
Gave the components the database's URL
Restarted the conductor and the dashboard with it; first run gave up its rights
Every component is ready
Then it reports what was restored on stdout:
Restored meridian into meridian from meridian-20261012T143005Z.meridian-backup.
The database meridian: <n> tables, <n> rows, every count as backed up:
config: <n> tables, <n> rows
book: <n> tables, <n> rows
street: <n> tables, <n> rows
lake: <n> tables, <n> rows
instrument: <n> tables, <n> rows
dashboard: <n> tables, <n> rows
<n> Secrets and the deployment's key, as they were: secret settings open with the settings key they were sealed with, and every sidecar trusts the dashboard's key.
Launched plugins whose images are not in the deployment's registry, which a backup does not hold: <instance> (<image>), …. Upload each again with `meridian plugin upload` from its directory; an edge plugin's own storage starts empty, and it gathers again what its vendor still serves.
The counts are given by store, and it reads each launched plugin's pod for at most two minutes and names only those whose image could not be pulled.
After a restore¶
- The same people, access, accounts, settings and book are there, and secret settings open with the restored settings key.
- Upload each launched plugin the report names again with
meridian plugin upload. - Where the deployment made its own key, it is now held in the Secret
<release>-meridian-runtime-key. - Its values are the ones it was backed up with,
backupamong them, so it backs up as before.
If a restore stops partway, the namespace holds what it made, and restore will not go into it
again. Empty the namespace, or restore into another with -n.
Restoring on the same cluster¶
After a namespace is deleted, the volume made from backup.path is left behind, Released.
Delete it before you restore; restore refuses while it is there, naming the command. The directory and the backups in it are
kept:
A reset of Rancher Desktop leaves no such volume.
With a database you run¶
When the database is one you run, and the backup does not include it, restore puts its URL back as it was, and the deployment reaches that database again. Its records are the ones the database holds, so restore it from your provider's own backups first if it was lost.
A backup taken with backup.database: true holds a dump of that database. Restore loads it
itself, into that database, which must be empty: it needs backup.image, the URL to load
with, and your right to add a container to a pod and exec into it, all checked before it changes
anything. The plan names the database by its host and name, never its credentials; stop anything
else running against it first. Restore adds a container of backup.image beside the conductor,
which reaches the database by the cluster's own path and is gone when the conductor restarts, loads
the dump in one transaction as the migrating role, and counts every table against the backup's
before the components are given the URL.