Skip to content

Backup & Disaster Recovery

Vesana has a built-in backup system under Admin → System → Backup & DR. It is the single standard — no separate sidecar, no manual pg_dump needed.

One system, four triggers

Every backup lands in ONE catalog, in ONE place, under ONE retention:

Type When
Scheduled Daily full dump (on by default, 02:00 UTC)
Manual "Back up now" button
Pre-restore Automatically before every restore (reversibility)
Pre-update Automatically before every online update by the updater

A backup is a full pg_dump (compressed .sql.gz) including the TimescaleDB hypertables (check history, logs). It lives in the Docker volume backup-data (/var/lib/vesana/backups).

FIELD_ENCRYPTION_KEY is the only thing that truly cannot be replaced

Encrypted DB fields (SNMP communities, SNMPv3 and SSH passwords) can never be decrypted again without this key. It must live outside the backup — in a password manager + a printed copy in a safe. Keep the backup dump and the FIELD_ENCRYPTION_KEY separate.

Schedule & retention

Under Admin → System → Backup & DR → Schedule & retention:

Setting Default Meaning
Daily backup on Automatic full dump once per day
Hour (UTC) 2 Hour at/after which the daily backup is taken
Max count 5 older backups are pruned beyond this
Max age (days) 90 older backups are deleted
Max disk (GB) 10 total cap; oldest pruned until under it

The strictest of the three retention limits wins. Pre-update and pre-restore backups count too. A watcher checks hourly whether the daily backup is due; a cleanup job runs every 6 h.

Off-site copy (important)

Backups live locally on this server. If the machine is lost entirely, so are they. For real disaster recovery you need an off-site copy:

  • In the UI click "Download" on a backup → saves the .sql.gz locally.
  • Or copy it out of the volume with your own script:
docker run --rm -v vesana_backup-data:/src -v $(pwd):/dst alpine \
  sh -c 'cp /src/*.sql.gz /dst/'
# then rotate to S3 / NAS / USB

Restore (on a running server)

In the UI: click "Restore" on a backup → type the hostname (type-to-confirm) → confirm 2FA (single-use token) → apply.

The restore is TimescaleDB-safe: it wraps the dump in timescaledb_pre_restore() / timescaledb_post_restore() and terminates open DB sessions so hypertable chunks come back correctly. A pre-restore backup of the current state is taken automatically first.

Restore = maintenance window

The restore overwrites the database and disconnects active sessions. All changes since the chosen backup are lost. Don't trigger it casually during operation.

Disaster recovery: fresh server

Goal: server crashes → fresh server → load yesterday's backup → back to yesterday's state, nothing lost except the current day.

  1. Provision a new server with setup.sh (as for a first install). On first boot the migrations run → empty schema + TimescaleDB extension are in place.
  2. Carry over FIELD_ENCRYPTION_KEY from the old .env (otherwise encrypted fields are unreadable after the restore). Likewise SECRET_KEY if existing logins should stay valid.
  3. Log in as super-admin → Admin → System → Backup & DR.
  4. "Upload backup" → pick the .sql.gz from your off-site copy. It shows up as a manual backup in the catalog.
  5. Click "Restore" on the uploaded backup → type the hostname → 2FA → apply.
  6. Verify: log in with the original admin, open a host with an encrypted snmp_community (value must be intact), check a service's check history (hypertable chunks must be present).

If an encrypted field looks empty/broken in step 6, the FIELD_ENCRYPTION_KEY was wrong.

Disaster drill

A restore you never test is hope, not a backup. Recommendation: run the fresh-server runbook on a separate VM once a quarter — including step 6.

See also