Zaroz
Operate services

Backups and restore of a service volume

A service that stores data has a volume. A backup is a snapshot of that volume that you take on demand, keep, and can roll the volume back to. These are your snapshots. Separately, Zaroz keeps its own disaster-recovery backups of the platform; they are not listed here and are not something you restore from.

The samples use the helper from Using the Zaroz API. {resource_id} is the id of the service's volume resource: list the service's resources with GET /service-instances/{id}/resources and pick the one of kind volume. Only services whose product has storage and whose cluster supports snapshots can take backups; check the backups entry in the service's capabilities. A service without storage answers 404, the same as one that is not yours.

Take a backup

curl

TerminalCode
B=/service-instances/$SERVICE/resources/$VOLUME/backups curl -sS -X POST "$ZAROZ_API$B" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"name": "before-upgrade"}' # 202: it runs in the background curl -sS "$ZAROZ_API$B" -H "$AUTH"
JSONCode
{ "backups": [ { "id": "…", "name": "before-upgrade", "status": "ready", "size_bytes": 482344960, "created_at": "2026-10-15T09:00:00Z", "completed_at": "2026-10-15T09:01:12Z", "restore_phase": null, "restore_started_at": null } ], "max_backups": 5 }

TypeScript

TypeScriptCode
import { call } from "./zaroz"; const B = `/service-instances/${SERVICE}/resources/${VOLUME}/backups`; await call("POST", B, { name: "before-upgrade" }); // omit the name for a timestamped default for (;;) { const { body } = await call("GET", B); const b = body.backups.find((x: any) => x.name === "before-upgrade"); console.log(b.status, `${body.backups.length}/${body.max_backups}`); if (b.status === "ready" || b.status === "failed") break; await new Promise((r) => setTimeout(r, 5000)); }

Python

Code
import time from zaroz import call B = f"/service-instances/{SERVICE}/resources/{VOLUME}/backups" call("POST", B, {"name": "before-upgrade"}) # omit the name for a timestamped default while True: _, body = call("GET", B) b = next(x for x in body["backups"] if x["name"] == "before-upgrade") print(b["status"], f"{len(body['backups'])}/{body['max_backups']}") if b["status"] in ("ready", "failed"): break time.sleep(5)
  • A backup starts as creating and becomes restorable at ready. failed stays in the list until you delete it.
  • max_backups is the ceiling per volume, published in the listing so you never hard-code it. Past it, creating answers a quota error: delete an old one first.
  • You cannot start a backup while another backup or a restore is running on the same volume (409).
  • A backup is a snapshot of the volume as it is at that moment. When you need a copy that is consistent at the application level (a database mid-transaction), stop writing first or take the database's own dump as well.

Restore

Restoring is irreversible. The workloads that use the volume are stopped, the volume's data is reverted to the snapshot, and then they start again: every change made since the backup was taken is lost. If you might want the current state back, take a backup of it first. The backup must be ready.

curl

TerminalCode
curl -sS -X POST "$ZAROZ_API$B/$BACKUP/restore" -H "$AUTH" # 202; poll the listing

TypeScript

TypeScriptCode
await call("POST", `${B}/${BACKUP_ID}/restore`); for (;;) { const { body } = await call("GET", B); const b = body.backups.find((x: any) => x.id === BACKUP_ID); console.log(b.status, b.restore_phase); if (b.status === "ready" && !b.restore_phase) break; await new Promise((r) => setTimeout(r, 10_000)); }

Python

Code
call("POST", f"{B}/{BACKUP_ID}/restore") while True: _, body = call("GET", B) b = next(x for x in body["backups"] if x["id"] == BACKUP_ID) print(b["status"], b["restore_phase"]) if b["status"] == "ready" and not b["restore_phase"]: break time.sleep(10)

While it restores the backup shows status: "restoring" and a restore_phase that moves through stopping_workloads, detaching_pre, maint_attaching, reverting, detaching_post, csi_retaining_pv, csi_recreating_pvc and restarting_workloads. It runs over several minutes; large volumes take longer. Do not start another backup or restore meanwhile (409). Use restore_started_at to show "restoring for N minutes".

Delete a backup

TerminalCode
curl -sS -X DELETE "$ZAROZ_API$B/$BACKUP" -H "$AUTH" # 204
TypeScriptCode
await call("DELETE", `${B}/${BACKUP_ID}`);
Code
call("DELETE", f"{B}/{BACKUP_ID}")

A backup that is creating or restoring cannot be deleted; wait until it is ready or failed. Deleting removes the underlying snapshot.

How full is the volume

curl

TerminalCode
curl -sS "$ZAROZ_API/service-instances/$SERVICE/resources/$VOLUME/volume" -H "$AUTH" # {"capacity_bytes": 10737418240, "used_bytes": 2147483648, "used_fraction": 0.2, # "used_label": "2.0 GiB of 10 GiB", "mount_path": "/data", "last_backup_at": "2026-10-15T09:01:12Z"}
TypeScriptCode
const v = await call("GET", `/service-instances/${SERVICE}/resources/${VOLUME}/volume`); console.log(v.body.used_label, v.body.last_backup_at);
Code
_, v = call("GET", f"/service-instances/{SERVICE}/resources/{VOLUME}/volume") print(v["used_label"], v["last_backup_at"])

capacity_bytes is the configured size (a quota, always present). used_bytes is measured inside the cluster and is null whenever it cannot be measured, so show "unknown" rather than zero. last_backup_at tells you whether your routine is working.

Before you rely on it

  • Backups belong to the volume and its subscription. Do not treat them as the only copy of anything you cannot lose if you might delete or cancel the service.
  • To keep a copy outside Zaroz, copy the files (see the file routes, or SFTP) or dump the database to object storage.
  • Automate it: a nightly job that creates a backup, waits for ready and deletes the oldest when you reach max_backups is a few lines of the code above. Use a token with services.manage scoped to that one service.
Last modified on