# Backups > Audience: administrators. This page is hidden from members and guests in the sidebar, the search > index, and the documentation export. It describes the backup service, where archives live, and how > to schedule and rotate them. DevPlace ships an enterprise-grade backup service that captures point-in-time copies of platform data as compressed `tar.gz` archives. Every backup runs as an asynchronous job in a subprocess off the request path, so creating a backup never blocks the server or slows requests. ## Targets A backup captures one of four targets: - **Database** - a consistent SQLite snapshot of the main database plus the Devii task and lesson databases. The snapshot uses SQLite's online backup API, so it is consistent even while the server is writing under WAL. - **Uploads** - every attachment and project file under the uploads directory. This is normally the largest target. - **Keys and config** - the VAPID notification keys and other small config artifacts. - **Full data directory** - the database snapshot, uploads, and keys in one archive. Regenerable and volatile data (job staging, locks, zip archives, container workspaces, search caches) is excluded because it can be rebuilt and would only bloat the archive. ## Where backups live Archives are written under the runtime data directory at `data/backups/`, sharded into a two-level `xx/yy` tree taken from the random tail of each backup's identifier so no single directory grows without bound. Each archive is named `target-YYYYMMDD-HHMMSS-id.tar.gz` and recorded with its size, file count, and a SHA-256 checksum for integrity verification. Staging happens under `data/backup_staging/` and is removed as soon as the archive is built. Because everything lives under the single data directory, pointing `DEVPLACE_DATA_DIR` at a mounted volume in production places backups on that volume automatically. ## Creating a backup Open **Admin -> Backups**, choose a target, and select **Create backup**. The dashboard shows the job moving from pending to running to done; when it completes a download link appears. Backups can also be created from the command line with `devplace backups run ` (the running server processes the enqueued job), or by asking Devii to back up a target. **Downloading is restricted to the primary administrator** - the earliest-created Admin, resolved by `database.get_primary_admin_uid` / `utils.is_primary_admin`. `GET /admin/backups/{uid}/download` returns `403` for every other administrator, and the `download_url` is withheld from them at every endpoint (their Download control renders disabled). Any admin may still create, schedule, and delete backups. ## Scheduling and rotation A backup schedule fires backups automatically. Each schedule has: - a **target** (database, uploads, keys, or full), - a **trigger** - either an interval in seconds or a 5-field cron expression (`minute hour dom month dow`), - a **keep_last** retention count - after each scheduled backup completes, older backups created by the same schedule beyond this count are deleted to reclaim space (0 keeps them all). Schedules are evaluated by the backup service, which runs only on the worker holding the service lock, so each scheduled backup fires exactly once. Create, edit, enable, disable, run, and delete schedules from the dashboard. ## Remote offload Every completed backup is shipped off-box to a Hetzner Storage Box over WebDAV (via `rclone`, independently of the `/backup` mount used for manual browsing) so a full local disk can never take the only copy of a backup with it. This runs as part of the backup service's own cycle, throttled to an interval separate from schedule triggers, and is configured from **Admin -> Services -> Backup** under the **Offload** group: - **Offload to remote storage** - master on/off switch. When off, nothing is uploaded and local copies are never purged (see below). - **Offload check interval** - how often the service looks for newly completed backups to upload and rotates old copies, in seconds. - **Local copies to keep per target** - across the whole target (database, uploads, keys, full, regardless of which schedule produced them), local archive files beyond this count are removed once a remote copy is confirmed. Default 1: the newest local archive per type stays on disk, everything older lives only on the Storage Box. - **Remote copies to keep per target** - archives on the Storage Box beyond this count are deleted, oldest first, per target. A backup is only ever considered "offloaded" after its upload is verified by an exact byte-size match against the size recorded when the archive was built - a failed or unverified upload is retried on the next cycle and never counted as safe. **Local retention only ever touches archives with a confirmed remote copy.** This also changes what schedule `keep_last` rotation does: it now skips any backup that has not yet been confirmed offloaded, so an aggressive `keep_last` can no longer delete the only surviving copy of a backup before it ever reached remote storage - if offload is turned off, schedule rotation simply stops deleting anything, which is the safe direction to fail in. Purging a local copy is different from deleting a backup: the archive's `backups` row and its `remote_uploaded_at` / `remote_path` persist, only the local file is unlinked. The dashboard and `devplace backups list` still show the backup; only its local download would need to come from the Storage Box copy first. A full delete (admin action, `keep_last` rotation, or `devplace backups clear`) still removes the record entirely and does not currently remove the matching remote copy - treat the remote side as the durable archive and the admin delete action as "no longer needed locally," not as "erase everywhere." Offload requires a one-time server-side setup step outside this dashboard: an `rclone` remote configured on the host (or inside the app container) pointing at the Hetzner Storage Box's WebDAV endpoint, with credentials. Until that remote exists, uploads fail every cycle (logged, retried automatically) and archives simply accumulate locally with no data loss - they just never leave the box, and local retention stays inactive the whole time since it never touches an unconfirmed backup. ## Storage visibility The dashboard reports the size and file count of every major data area (database, uploads, attachments, project files, keys, zip archives, search data, container workspaces, and the backups themselves), the total data-directory footprint, the total size and count of stored backups, and the underlying disk usage (total, used, free, and percent). The scan runs in a worker thread and is cached briefly so it never blocks page rendering. ## Retention and deletion Backup archives are permanent operational artifacts: unlike the async job rows that track them, an archive is never swept away by job retention. It is removed only when an administrator deletes it, when a schedule rotates it out via keep_last, or by the `devplace backups clear` command. Deleting a backup is a hard delete that unlinks the archive file and reclaims its disk space immediately, and is recorded in the audit log. ## Restore This service creates and stores backups; it does not restore them into a live server, because overwriting the database or uploads while the application is running risks corruption. To restore, stop the server, unpack the relevant archive over the data directory (each archive is rooted at `database/`, `uploads/`, or `keys/`), verify the SHA-256 checksum recorded for the backup, and start the server again. ## Command line - `devplace backups list` - list recorded backups with target, status, size, and filename. - `devplace backups run ` - enqueue a backup for the running server. - `devplace backups prune` - remove backup records whose archive file is missing. - `devplace backups clear` - delete every backup archive and record.