Backup and restore¶
Danbyte backs itself up and restores itself from Settings → Backups. A backup is one encrypted archive that holds the database, the uploaded media and the non-secret deployment configuration. A restore replaces the live data with an archive while the site shows the maintenance page.
This complements an external backup of the host; it does not replace one.
The archive does not contain the two secret keys, so keep .env somewhere
safe as well.
What is in an archive¶
| Component | Contents |
|---|---|
db |
pg_dump of the whole database: every object, account and setting |
media |
MEDIA_ROOT (images, documents, thumbnails) and plugins_local/ |
config |
deployment settings minus secrets, enabled timers, installed plugins, the non-secret .env keys |
Every archive also carries a manifest.json: Danbyte version, git commit,
the list of applied migrations, per-type object counts, file count and
sizes. The Backups page shows it, and a restore checks it first.
Not in an archive, ever: DJANGO_SECRET_KEY, MONITORING_SECRET_KEY,
DB_PASSWORD, SMTP passwords, TLS keys.
The key rule¶
Archives are encrypted with AES-256-GCM under a key derived from
MONITORING_SECRET_KEY - the same key that encrypts credentials inside the
database. There is no unencrypted option.
To restore on another host, copy MONITORING_SECRET_KEY to the new host
first. An archive made under another key is refused at upload and at
preview with a clear message, instead of restoring a database whose
credentials silently decrypt to nothing.
The file format is .dbk: a small header (magic, salt, key check) followed
by the encrypted stream. Nothing else reads it; download it, keep it, upload
it back.
Targets¶
A target is where archives are stored.
- Local directory - the default target, seeded on first start at
DANBYTE_BACKUP_DIR(default<install>/../danbyte-backups,/app/backupsin the container stack). Files are written0600. - S3-compatible bucket - bucket, optional prefix, endpoint URL (blank for
AWS), region and an access key pair. Needs
boto3in the environment; the Test button says so when it is missing.
Test writes and removes a marker on the target. A failing target shows its last error on the Backups page. One target is the default; Back up now and uploads use it.
Scan for archives adopts .dbk files on the target that have no row:
archives copied into the directory by hand, or a whole directory carried
over from another host. Each is opened for its manifest; one made under
another key is skipped.
Schedules¶
A schedule is a cadence (hourly, daily, weekly, monthly, with a wall-clock time), the components to include, a target, a retention rule and the notification channels that hear about its runs.
- Retention: keep at most N archives and/or nothing older than N days. It runs after each successful run and only touches that schedule's own backups. Protected backups are never pruned.
- Run now makes an extra backup without moving the schedule's next occurrence.
- The tick is the
danbyte-backupstimer (every 5 minutes on systemd, theschedulercontainer in Docker). A schedule fires once per occurrence.
Backups¶
The table lists every archive with its kind, components, size, status and who made it. Kinds:
| Kind | Made by |
|---|---|
| Manual | Back up now, or manage.py backup_now |
| Scheduled | a schedule |
| Before upgrade | the upgrade scripts, before migrating (the newest three are kept - see Housekeeping) |
| Before restore | every restore, of the state it is about to replace (protected) |
| Uploaded | Upload backup |
Row actions: Steps (what the run did), Download, Restore…, Protect / Unprotect, Delete. A protected backup cannot be deleted or pruned.
Uploads and downloads go through nginx's buffers: nginx receives the whole
upload before Danbyte reads it, and takes the whole download before sending
it on, so a slow link never holds a web worker past its timeout. nginx needs
free space for one archive in its temporary directory
(/var/lib/nginx on Debian and Ubuntu).
Backups run in the RQ worker (danbyte-workers); a queued backup that
never starts means the workers are down - see Jobs.
Housekeeping¶
An install collects files nothing else removes. The Housekeeping card on
Settings → Backups shows what is stale, how much space it takes, and the
size of the backups, log, wheel and media folders; Clean up now removes it.
The same pass runs every night (the danbyte-prune timer) and at the end of
every upgrade.
| What | Kept |
|---|---|
| Before-upgrade backups | the newest Upgrade backups kept (default 3) |
Code rollback archives (code-pre-<version>-<time>.tgz, from bundle upgrades) |
the same number, newest first |
Wheels in vendor/wheels that no installed package came from |
none - a bundle brings its own |
| The downloaded upgrade bundle, after a failed attempt | an hour, for a retry |
| Backup and restore work folders a killed job left | a day |
Rotated log files (*.log.1, *.log.2.gz, ...) |
Rotated logs kept days (default 30; 0 = forever) |
The live log files are logrotate's to size; the systemd journal is the
server's (journalctl --disk-usage, and SystemMaxUse in journald.conf).
Manual, scheduled and uploaded backups are never touched here - schedules have
their own retention, and the rest are yours.
Installs upgraded with bundles before 0.16.12
A bundle upgrade used to copy the new release over the old one without
removing anything, and to pack the bundle and the media folder into every
rollback archive. The first housekeeping run after upgrading removes the
old archives and wheels; stale build files in frontend/dist and
staticfiles go with the next bundle upgrade, which now replaces those
folders whole. To see it without removing anything:
manage.py housekeeping --dry-run.
Restore¶
Restore… on a backup opens the preview:
- Checks: the archive opens with this host's key; the components are
present; every migration in the archive is known to this version (an
archive from a newer Danbyte is refused - upgrade first); the database
role owns the database; enough disk under
DANBYTE_BACKUP_DIR; no upgrade is running. - This replaces: object counts from the archive, so you know what you are about to overwrite.
- The components to restore, and the deployment name typed to confirm.
Then, in the worker, in order:
- Take the upgrade lock (an upgrade or a second restore is refused while it is held).
- Make a Before restore backup of the current state and protect it.
- Raise the maintenance flag. Every request answers
503withRetry-After, which nginx turns into the "Danbyte is updating" page; only the health probe (with"maintenance": true) and the restore-status endpoint stay open, and the 503s are not logged as errors. The running restore's status is answered from Redis, so the dialog keeps showing progress even while the database has no tables. - Terminate the other database sessions, drop and recreate the
publicschema,pg_restorethe dump and runmigrateforward. - Reconcile: the restored database predates the restore, so the rows describing it - the target, the archive, the Before restore backup and the run itself - are written back, and every other archive on the target without a row (backups made after the archive's point in time) is adopted. Nothing on disk is lost by restoring.
- Extract the media tree next to the live one and swap them; the old tree is kept until the run succeeds.
- Rebuild the search index, flush the RQ queues, clear the flag, release the lock.
Nothing restarts. Django holds no schema state between requests, so the
site is live again the moment the flag drops; the dialog offers Reload.
Logged-in sessions survive because DJANGO_SECRET_KEY does not change.
If a step fails after the schema was dropped, the run is marked failed with the step and error, the maintenance flag is cleared, and the Before restore backup is right there in the list to restore from.
Moving to a new host¶
- Install Danbyte on the new host and set the same
MONITORING_SECRET_KEY. - Download the archive from the old host, Upload backup on the new one.
- Restore… it. The database, media and configuration come across;
re-enter the secrets that live in
.env.
From the shell¶
manage.py backup_now --components db,media,config [--target NAME] [--kind manual|pre_upgrade]
manage.py run_backups # the schedule tick
manage.py restore_backup <id> [--components db,media] --yes
backup_now runs inline (no worker) and prints the archive path - the
upgrade scripts call it before migrating and stop when it fails, unless
DANBYTE_SKIP_BACKUP=1. restore_backup is the same engine for an admin
without a browser.
Notifications¶
A schedule's channels get one line per run: success (components, size, target) or failure (the step and the error). Every channel kind works - email, webhook, Slack, Teams, Discord, PagerDuty. A failed run of any kind also mails the deployment digest recipients (Settings → Email). A manual run that succeeds notifies nobody.
Reverse proxy¶
Archives pass through /api/backups/ and can be several gigabytes. The
nginx templates the installer writes carry a location for it; a hand-managed
nginx config needs it added before the existing /api/ location (with ^~
when that location is a regex):
location ^~ /api/backups/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering on;
proxy_max_temp_file_size 10240m;
proxy_read_timeout 600s;
client_max_body_size 8g;
}
With buffering on, nginx takes a whole upload before it hands it to Danbyte,
so a slow upload never holds a web worker; it needs free space for one archive
in its temporary directory. A block added before 0.16.12 has
proxy_request_buffering off; - change it to the block above (Settings →
Updates lists that step until the site has it).
proxy_max_temp_file_size takes k or m: nginx refuses 10g there,
although client_max_body_size accepts it.
Then sudo nginx -t && sudo systemctl reload nginx. Without it, uploads over
the server's default body limit and slow downloads fail; backups themselves
still run.
Docker¶
The compose stack mounts a backups volume and the media volume into
backend and workers; DANBYTE_BACKUP_DIR is /app/backups. A restore
runs inside the workers container against the postgres service over the
network and never restarts a container. Copy archives out with
docker compose cp workers:/app/backups/<file> . or use Download.