---
sidebar_position: 3
title: Backup & restore
---

# Backup & restore

`backup.sh` and `restore.sh` ship next to the compose file in your install directory (the installer drops them there). Together they let a dead VM — or a fat-fingered `docker compose down -v` — never mean "start over and reconnect 50 repos".

## What a backup contains

One timestamped tarball holds the four things that together **are** your install:

1. **ScyllaDB volume** (`scylla-data`) — all metadata: integrations + their encrypted connect tokens, deployed agents (graph/env/schedule/prompts), repo→agent links, projects, members, access tokens, review-memory, execution history, stores.
2. **Object-store volume** — `seaweed-data` (SeaweedFS, the default) or `minio-data` on a legacy box. Run artifacts + per-node logs, datasets, marketplace icons. The active store is detected from `.env` (`ZIBBY_OBJECT_STORE`) / volume presence; `restore.sh` handles either.
3. **Sidecar data volumes** (`zibby-sidecar-*-data`) — content that exists **nowhere else** and cannot be re-derived: a knowledge base's indexed documents, a sidecar's encrypted OAuth token store. They are discovered by convention, so a sidecar you add later — including one you brought yourself — is included with no configuration. Skip them with `--skip-sidecar-data` (or `ZIBBY_BACKUP_SIDECAR_DATA=false` for scheduled backups) if the size is a real problem; those sidecars then restore **empty**, and `restore.sh` says so out loud.
4. **`.env`** — **critical**. It holds `ENCRYPTION_KEY`, without which every encrypted token/secret in ScyllaDB is unrecoverable, plus `JWT_SECRET`, the object-store root creds, and integration tokens. A ScyllaDB restore without the matching `ENCRYPTION_KEY` is useless, so `.env` travels **inside** the backup — which is also why the backup is highly sensitive.

## Take a backup

```bash
cd zibby-selfhosted
./backup.sh                         # → ./backups/zibby-selfhosted-<timestamp>.tar.gz
./backup.sh --out /mnt/backups      # write elsewhere
./backup.sh --encrypt               # encrypt at rest with age or gpg (prompts for a passphrase)
./backup.sh --s3 s3://mybucket/zibby   # also upload with YOUR aws creds (nothing goes to Zibby)
```

Consistency + downtime: for a single-VM box the safe, simple approach wins — the script briefly **stops the writers** (control-plane + datastores + any running sidecars, whose single-writer file databases would otherwise be captured mid-write), `nodetool flush`es Scylla so the commitlog is on disk, tars the volumes read-only from a throwaway helper container, then restarts everything. Downtime is typically seconds to a few minutes (plus Scylla's ~1–2 min cold-boot on restart). A trap guarantees the stack restarts even if the backup errors out. Volume names are compose-project-prefixed and **discovered at runtime**, so it works regardless of your install-dir name.

Copy the tarball **off the box** — it is your only recovery path, and it contains the encryption key.

:::tip
An upgrade takes its own safety snapshot into `<install-dir>/backups/upgrade-<id>/` before touching anything, and there is an `auto-backup.sh` you can cron for scheduled snapshots.
:::

## Restore

Onto the same box after data loss, or onto a brand-new VM:

```bash
./restore.sh zibby-selfhosted-<timestamp>.tar.gz          # interactive confirm
./restore.sh backup.tar.gz --dir /opt/zibby --force        # scripted
./restore.sh backup.age                                    # decrypts first (needs age/gpg)
```

`restore.sh` restores **both** data volumes and the `.env` (so the restored, encrypted data is readable). Volume names are discovered at runtime, or created if this is a fresh box.

:::danger Restore wipes current data
Restoring **overwrites** this install's volumes. The script refuses to run without an explicit confirmation (interactive `y/N`, or `--force`).
:::

A MinIO-era bundle restored onto a current (post-SeaweedFS-flip) release comes up on the legacy MinIO override; the next installer run auto-migrates it to SeaweedFS — see [Storage](./storage).
