Backup and Restore
Admin → Backup (/admin/backup) is where you configure the built-in backup worker, watch that it is actually running, and — in an emergency — request a restore.
Two menu entries lead here:
| Menu entry | What it is | Permission |
|---|---|---|
| Backup | The screen itself | perm:backup:admin |
| Restore (child of Backup) | Not a separate screen. The row exists only to carry the restore permission; the restore action appears on the snapshot list of the Backup screen for users who hold it. | perm:backup:restore |
There is no admin bypass: without perm:backup:admin the screen is not reachable at all, and without perm:backup:restore the restore action is not rendered and the restore endpoints refuse the call. Downloading a snapshot additionally requires perm:backup:export.
Before you start
Backups are executed by the dedicated opsee-backup container, not by the application. That container ships idle — a fresh install never silently writes backups nowhere. See Deployment Overview and, for the closed bundle, Closed Release. If the container is not running, the settings on this screen are stored but nothing acts on them.
Credentials you enter here are encrypted with the server's MASTER_ENCRYPTION_KEY. If that key is missing or invalid, the screen shows a banner saying credential storage is unavailable and existing backups keep running from their legacy environment references.
The three tabs
| Tab | Contents |
|---|---|
| Overview | The "am I protected?" summary — last successful backup, next scheduled run, destination, snapshot count and retention — plus the read-only snapshot list. |
| History | Backup requests and restore requests, with their status and messages. |
| Settings | Schedule, retention, destination and credentials. |
The active tab and the History filter are kept in the URL (?tab=, ?history=), so a link to a specific view can be shared.
Configuring backups
Everything below lives on the Settings tab. The Save configuration button stays disabled until something changes, and leaving the page with unsaved changes asks for confirmation.
Schedule
- Enabled — the master switch. While off, the Overview shows Backups are disabled.
- Start hour (UTC) and Interval — backups run every N hours from the start hour. The interval must divide 24, so the choices are 1, 2, 3, 6, 12 and 24 hours. The screen previews the resulting slots in your local time and in UTC.
- Leaving the schedule unset means backups only run manually, from the Request backup button.
- Stale after (hours) is only an alert threshold: a backup older than this is flagged Stale on the Overview. It does not trigger anything.
Retention
Keep daily, Keep weekly and Keep monthly define how many snapshots survive. Older snapshots are removed automatically by the worker; the Overview restates the rule in words and shows when it was last applied.
Destination
Pick a Destination type; the form then asks only for the fields that type needs. All types write a restic repository, so the data is encrypted before it leaves the container — the destination only ever holds ciphertext. Every type therefore needs a Repository password in the Credential section.
| Destination type | Fields you must supply | Credential |
|---|---|---|
| Encrypted local folder | Path is fixed to /var/opsee/backups inside the backup container (the infra_opsee-backup-local docker volume) | Repository password |
| SFTP server | Host, port (default 22), username, path | SSH private key |
| S3 / object storage | Bucket, endpoint URL, region, path/prefix | AWS access key ID + secret access key |
| SMB / Windows share | Host, share name, username, optional folder, port (default 445), optional domain/workgroup | Destination password |
| FTPS (FTP over TLS) | Host, port (default 21), path | Destination password |
| SFTP (password) | Host, port (default 22), username, path | Destination password |
| WebDAV (HTTPS) | Endpoint URL (must start with https://), optional username, optional sub-folder | Destination password |
The last four go through rclone and are encrypted transports only: plain FTP, SMB1 and http:// WebDAV are not offered at all, not even as a toggle.
To change where a local folder backup physically lands on the host, override the docker volume — the in-container path is deliberately locked in the UI, because any other value would be written to the container's ephemeral layer and lost on the next rebuild.
Credentials
Credentials are write-only. Once saved, the screen shows Set; the API never returns the stored value. Use Replace to enter a new one and Clear to remove it — clearing warns you that backups will fail until a new credential is provided.
Switching destination type clears nothing automatically but does prompt you to re-enter the credentials the new type needs.
If required fields are missing, the Save area lists them ("Destination required: …", "Credentials required: …") instead of failing silently.
Running a backup on demand
Request backup (top right) queues a request; the backup host picks it up on its next cycle. It does not run a backup immediately, and it is never a restore.
Queued and finished requests appear under History, with their origin (Scheduled or Manual), status, duration and message. From there you can:
- Cancel a pending request. In-flight and finished requests are unaffected.
- Clear history — permanently deletes done, failed and cancelled rows older than a threshold you choose (1–3650 days, default 30). Pending and in-progress requests are never deleted.
Verifying that you are protected
The Overview tab is the check:
- Last successful backup — with a Stale chip when it is older than the stale threshold.
- Next scheduled backup — the next slot, or No schedule set.
- Snapshots — how many exist in the repository and the size of the newest.
The snapshot list is the authoritative proof that data actually reached the destination: it is read from the repository itself (cached, refreshed roughly every 15 minutes) and shows the snapshot ID, creation time, host, size and tags. An empty list with a green schedule means the worker is not writing where you think it is — check its logs on Workers and System Logs.
There is deliberately no "test restore" button. The only true verification of a restore is running one against a throwaway or staging environment.
Downloading a snapshot
The download action on a snapshot row asks the worker to stage a .tar.gz export and then downloads it. It can take a while for a large backup, only one export runs at a time, and the prepared link expires — start it again if it does. This is a download, not a restore; it requires perm:backup:export because it is full-database egress.
Deleting a snapshot
The delete action permanently removes that snapshot from the backup repository. It is irreversible — the repository has no trash — and you must type the snapshot ID to confirm. It removes a restore point only: no live OpSee or Keycloak data is touched.
Restore
:::danger A restore destroys data
A restore drops and recreates the live OpSee and Keycloak databases and reloads them from the snapshot. Everything created since that snapshot is permanently lost. There is no undo.
:::
Restore is emergency-only, opt-in and operator-assisted. Requesting one from this screen only queues it — it is not a one-click restore, and a request sitting at Pending (awaiting operator) is the normal, expected state.
Requesting a restore
Visible only to users with perm:backup:restore.
- On the Overview tab, find the snapshot in the snapshot list and use its restore action.
- The dialog restates the warning and shows the snapshot ID, creation time, source host and size.
- Type the snapshot ID exactly to confirm.
- Leave Create a fresh backup before restoring ticked (recommended) so the current databases are captured before they are overwritten.
- Request restore. Track it under History → Restore requests.
A pending restore can be cancelled from the same list, as long as it has not started.
Why a request stays pending
The restore worker runs only when three independent gates all hold:
| Gate | Set where |
|---|---|
The BACKUP_RESTORE_ENABLED system parameter is true (defaults to false) | System Parameters — while false the worker never even claims the request |
RESTORE_DB_SUPERUSER and RESTORE_DB_SUPERUSER_PASSWORD are present in the opsee-backup container's environment | Server-side, for the duration of the restore window only |
| The snapshot's source host matches this instance | Automatic — the worker refuses a snapshot taken on another environment |
When the first gate is off, the screen says so explicitly: "Restore requests will stay pending: the restore worker is disabled on this instance."
The application must also be stopped before the restore runs, and the restore superuser must be removed again afterwards. The full sequence — stop the stack, enable the gate, run, restart, verify, tear the opt-in back down — is in the repository runbook docs/runbooks/restore-from-backup.md. Do not improvise around it.
Restore applies to the full self-hosted Compose and Kubernetes stacks only. Edge and local stacks do not run the backup worker, so there is no restore path there.
Related
- Workers — the
backupworker's health, interval and last error - System Logs — what the backup worker logged, filtered by source
- System Parameters —
BACKUP_RESTORE_ENABLEDand the retention parameters - Deployment Overview — where the
opsee-backupcontainer fits