Operator guide

Backup & restore

What a Nosdesk backup contains, how to create one, what to keep alongside it, and how to restore from the admin UI or the CLI.

A Nosdesk backup is a single ZIP holding your database rows and every uploaded file. You create one from the admin UI and download it; later you restore it onto a matching instance, either from the admin UI or, when the UI isn’t reachable, from nosdesk-cli.

This is an application-level backup, not an infrastructure snapshot. It’s portable across hosts, and it’s what the restore tooling reads. To snapshot at the storage layer instead, see Infrastructure-level snapshots.

What a backup contains

  • The database. Every base table in the public schema, found by introspection rather than a hardcoded list, so tables added by a future upgrade come along automatically. Partition children are covered by their parent, and Diesel’s migration ledger is left out (it’s rebuilt on restore).
  • Uploaded files. Everything under the uploads directory: avatars, attachments. Image thumbnails are skipped, since they’re rebuilt automatically after a restore.
  • A manifest. The backup format version, the Nosdesk version and schema hash it was taken at, per-table row counts and checksums, and the file count and total size. The restore preview reads this back to you before you commit.

The search index isn’t in the archive either; like thumbnails, it’s rebuilt from the restored content.

Standard vs. sensitive (encrypted) backups

The Include sensitive data toggle decides whether authentication material travels with the backup.

  • Off (default): a plaintext ZIP. Credential columns (password hashes, MFA secrets, recovery codes, refresh and reset tokens, API token hashes, webhook signing secrets) are stripped out, so a leaked archive can’t be used to recover them. After restoring one of these, users have to reset their passwords and re-enrol MFA.
  • On: the whole archive is sealed with AES-256-GCM under a password you choose, and those credential columns are kept. Take one of these when you want logins and MFA to keep working after a restore. Lose the password and the data is gone; there’s no recovery path.

What is not in a backup

The archive doesn’t include your .env, and two values in it matter for an encrypted-sensitive restore:

  • MFA_KEK_* — the keyring that encrypts MFA secrets, channel credentials, and plugin secrets at rest. The backup keeps those columns as they are, still wrapped by your KEK, so restoring onto an instance with a different MFA_KEK leaves them undecryptable.
  • JWT_SECRET — existing sessions are signed with it.

So keep your .env backed up alongside the archive, and restore onto an instance carrying the same MFA_KEK_* and JWT_SECRET. See the Configuration reference.

Creating a backup

In the dashboard, go to Admin → Backup & Restore → Create Backup (platform admins only). To capture credentials, turn on Include sensitive data and set an encryption password. Then download the archive from the Recent Backups list.

Backups are written inside the server’s uploads volume, at ${UPLOAD_DIR}/backups — the same volume as your uploaded files. Lose that volume and the in-place backups go with it, so download the archive and keep a copy off the host.

There’s no schedule: you trigger backups yourself. To automate, drive the admin backup HTTP endpoints from your own cron or job runner and ship the downloaded archive to off-site storage.

Restoring

Restore is destructive: matching tables are replaced. The whole thing runs in one transaction, so if it fails it rolls back and you’re never left with a half-restored database.

Two prerequisites:

  • Matching schema. The archive carries the schema hash of the build it came from, and restore refuses a mismatch unless you override it, so restore onto the same Nosdesk version you backed up from. (A different app version is only a warning in the preview; an incompatible backup format is refused outright.)
  • Matching secrets, for an encrypted-sensitive backup — see What is not in a backup above.

From the admin UI

Admin → Backup & Restore → Restore from Backup. Drag in the archive and Nosdesk shows a preview: when it was taken, the version, file count and size, per-table row counts, and any warnings. Enter the decryption password if the backup is encrypted, then confirm. The UI restores over existing data.

From the CLI

Use the CLI when the UI isn’t reachable: a fresh instance, or an admin who’s locked out. It’s gated on shell access, not the network.

# Copy the archive into the running container, then restore it
docker compose cp backup.zip nosdesk:/tmp/backup.zip
docker compose exec nosdesk nosdesk-cli db restore /tmp/backup.zip --password 'your-password'

nosdesk-cli db restore flags:

  • --password <PASSWORD> / --password-env <VAR> — decryption password for an encrypted backup (the env form keeps it out of your shell history).
  • --yes — skip the confirmation prompt, for scripts.
  • --force — allow restoring over a non-empty database. Without it the CLI refuses once a users row exists, to stop an accidental overwrite. The admin UI assumes this, since admin auth is the gate there.
  • --ignore-schema-mismatch — proceed despite a schema-hash mismatch. Only reach for this once you’ve confirmed the schemas are compatible, e.g. a no-op migration landed between versions.

The CLI needs DATABASE_URL and the encryption env vars (the same .env the server uses), and it prints the tables, records, and files restored when it’s done.

Infrastructure-level snapshots

For point-in-time recovery of the whole stack as it stands, you can back up at the storage layer instead:

  • pg_dump, or a volume snapshot, of the postgres_data volume, and
  • a copy of the backend_uploads volume.

This brings the stack back exactly as it was, but nosdesk-cli db restore can’t read it — that command expects the application archive above. Reach for the application backup when you want a portable, version-checked restore, and for storage snapshots when you want fast infrastructure rollback.

Test your restores

Whichever path you use, restore a backup into a throwaway instance now and then and check that it boots, that you can log in, and that your data’s there. A backup you’ve never restored is a guess.