Skip to content

Backup & restore

Vesana writes three classes of data you must back up:

Class Where Recoverable without backup?
Database (Postgres) Compose volume pgdata No
Configuration DB + .env (secrets) Partly — .env can be regenerated, but FIELD_ENCRYPTION_KEY is irreplaceable
Wiki attachments / uploads Volume uploads No

FIELD_ENCRYPTION_KEY is the only thing that can't be replaced

Encrypted DB fields (e.g. SNMP communities) can never be decrypted again without the key. The key must live outside the backup volume.

Automatic backup (sidecar)

Vesana ships an optional backup sidecar in the compose stack.

Enable

docker compose -f /opt/vesana/docker-compose.prod.yml --profile backup up -d

Configure

In .env:

Variable Default Meaning
BACKUP_SCHEDULE 0 2 * * * Cron expression (daily 02:00 UTC)
BACKUP_RETENTION_DAYS 7 Files older than X days are deleted
BACKUP_API_TOKEN empty Optional: admin token for additional config export

Restart the sidecar after changes:

docker compose -f /opt/vesana/docker-compose.prod.yml restart backup

What's backed up

  1. Database dumppg_dump as compressed SQL: backup-YYYYMMDD-HHMMSS.sql.gz
  2. Config export (if BACKUP_API_TOKEN is set) — JSON snapshot of tenants, hosts, profiles, alert rules etc. via /api/v1/admin/export
  3. Compose file snapshot — the docker-compose.prod.yml active at backup time

All backups land in volume backups, mounted at /opt/vesana/backups.

Inspect

ls -la /opt/vesana/backups/
docker compose -f /opt/vesana/docker-compose.prod.yml run --rm backup ls -la /backups/

Manual backup

Without the sidecar:

cd /opt/vesana

docker compose -f docker-compose.prod.yml exec postgres \
  pg_dump -U vesana vesana | gzip > backup-$(date +%Y%m%d-%H%M%S).sql.gz

Config export via API:

curl -H "Authorization: Bearer <ADMIN_JWT>" \
  https://your-domain.tld/api/v1/admin/export \
  > config-export-$(date +%Y%m%d).json

Storing safely

Content Where Who needs access
pgdata backups External NAS, S3, Veeam, USB disk in safe Backup operator
FIELD_ENCRYPTION_KEY Password manager Lukas / operations owner
SECRET_KEY (JWT) Password manager Only for disaster recovery
.env whole Encrypted container, separate from DB backup Backup operator

Don't put keys in the same backup as the database

Whoever has both the database and the FIELD_ENCRYPTION_KEY can read SNMP communities and other encrypted fields in clear. Separate them.

Restore

Database

cd /opt/vesana

# 1. Stop writing services
docker compose -f docker-compose.prod.yml stop api receiver worker

# 2. Restore backup
gunzip -c backup-YYYYMMDD-HHMMSS.sql.gz | \
  docker compose -f docker-compose.prod.yml exec -T postgres \
    psql -U vesana vesana

# 3. Start services
docker compose -f docker-compose.prod.yml up -d

For a fresh-instance restore, clear pgdata first:

docker compose -f docker-compose.prod.yml down
docker volume rm vesana_pgdata
docker compose -f docker-compose.prod.yml up -d postgres
# wait until postgres is healthy
gunzip -c backup-YYYYMMDD-HHMMSS.sql.gz | \
  docker compose -f docker-compose.prod.yml exec -T postgres \
    psql -U vesana vesana
docker compose -f docker-compose.prod.yml up -d

Config import

curl -X POST \
  -H "Authorization: Bearer <ADMIN_JWT>" \
  -H "Content-Type: application/json" \
  -d @config-export.json \
  https://your-domain.tld/api/v1/admin/import

A full config import overwrites existing tenants and hosts. Only import into an empty instance.

Disaster drill

A restore that's never been tested is hope, not backup. Do this once per quarter.

# 1. Test server / local VM with the latest backup
# 2. Make latest backup tarball + .env (with FIELD_ENCRYPTION_KEY) available
# 3. Run setup script, copy SECRET_KEY and FIELD_ENCRYPTION_KEY from original .env
# 4. Clear pgdata, restore via gunzip as above
# 5. Test login with original admin
# 6. Open at least one host, view a service with encrypted snmp_community → must work

If step 6 fails (snmp_community is masked even though a value should be there), the FIELD_ENCRYPTION_KEY from backup isn't the right one.

  1. Backup sidecar active for daily DB dumps
  2. FIELD_ENCRYPTION_KEY outside in password manager + printout in safe
  3. Weekly off-site copy of backup files (S3, NFS, USB rotation)
  4. Quarterly drill on a separate machine

Next