8.2 KiB
Production backup and recovery
Dragon's Stash backs up its PostgreSQL database, durable STL uploads, and the worker and bot Telegram (TDLib) session volumes to an encrypted Restic repository on a Synology NFS share. Docker volumes are local runtime storage; they are not, by themselves, backups.
The backup job runs as root because it reads the Restic password file and
mounts host paths into the backup container. Keep the Synology share on the
private network. Do not expose NFS to the Internet.
Protected data and limits
Each snapshot contains:
- a custom-format PostgreSQL dump;
- the
manual_uploadsvolume at/data/uploads(including newly completed manual STL uploads); - the worker TDLib session volume; and
- the bot TDLib session volume.
New manual-upload STL files are retained in manual_uploads and can therefore
be included in future backups. STL files that an older worker run already
deleted cannot be recovered by this backup feature. Their legacy database rows
are reported as warnings during restore validation; only files marked as
retained must be present in a restored snapshot.
1. Configure the Synology NFS share
On the Synology DSM host:
- In Control Panel > Shared Folder, create a shared folder named
dragonsstash-backups. - In Control Panel > File Services > NFS, enable NFS.
- Edit the new shared folder and add an NFS Permissions entry for the Docker host's fixed private-network IP address. Grant read/write access. Use the least permissive squash and authentication settings that work for the root-run backup job, and do not use a broad subnet or public address.
- Record the NFS export path shown by DSM (for example,
/volume1/dragonsstash-backups).
On the Docker host, install the NFS client package for its distribution, create
the mountpoint, and mount the export. Replace NAS_IP and the export path with
the values for the Synology:
sudo mkdir -p /mnt/dragonsstash-backups
sudo mount -t nfs -o nfsvers=4 NAS_IP:/volume1/dragonsstash-backups /mnt/dragonsstash-backups
For a persistent mount, add an /etc/fstab entry using the DSM export path:
NAS_IP:/volume1/dragonsstash-backups /mnt/dragonsstash-backups nfs nfsvers=4,_netdev,nofail,x-systemd.automount 0 0
Then verify that the host sees an actual writable mount rather than an empty local directory:
mountpoint /mnt/dragonsstash-backups
touch /mnt/dragonsstash-backups/.write-test
rm /mnt/dragonsstash-backups/.write-test
The scheduled backup refuses to run if BACKUP_MOUNT_PATH is not a mountpoint
or is not writable.
2. Create secrets and host configuration
Create a root-readable Restic password file. Generate and store a strong, unique repository password by your approved secret-management process; never commit it, place it in the Compose file, or paste it into shell history.
sudo install -d -m 0700 -o root -g root /etc/dragons-stash
sudo install -m 0600 -o root -g root /dev/null /etc/dragons-stash/restic-password
sudoedit /etc/dragons-stash/restic-password
Create a local staging directory. It must be on host-local storage, not the NFS share, and must have enough room for a PostgreSQL dump and restore staging data.
sudo install -d -m 0700 -o root -g root /var/lib/dragons-stash/backup-staging
Create the systemd environment file at /etc/dragons-stash/backup.env and keep
it root-only. It supplies both the host wrapper and the profile-gated backup
container:
sudo install -m 0600 -o root -g root /dev/null /etc/dragons-stash/backup.env
sudoedit /etc/dragons-stash/backup.env
Set these production values (replace the example paths and retention period as needed):
BACKUP_MOUNT_PATH=/mnt/dragonsstash-backups
BACKUP_STAGING_PATH=/var/lib/dragons-stash/backup-staging
BACKUP_RESTIC_PASSWORD_FILE=/etc/dragons-stash/restic-password
BACKUP_REPOSITORY=/backup/restic
BACKUP_RETENTION_DAYS=30
BACKUP_APP_VERSION=unknown
BACKUP_REPOSITORY is evaluated inside the backup container, where the NFS
share is mounted at /backup; /backup/restic keeps the Restic repository in
the Synology share. BACKUP_APP_VERSION is optional metadata. The production
Compose environment must also retain its existing database and application
secrets; do not add any secrets to Git.
3. Install the nightly systemd job
The supplied unit assumes the production Compose checkout is
/opt/stacks/DragonsStash. If your deployment lives elsewhere, update the
installed service's WorkingDirectory and ExecStart to the corresponding
absolute paths before enabling it.
Install and start the units:
sudo install -m 0644 deploy/systemd/dragons-stash-backup.service /etc/systemd/system/
sudo install -m 0644 deploy/systemd/dragons-stash-backup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now dragons-stash-backup.timer
sudo systemctl start dragons-stash-backup.service
sudo journalctl -u dragons-stash-backup.service -n 100 --no-pager
The timer runs nightly at 03:00 with up to 15 minutes of randomized delay and catches up after downtime. The first run can take a long time because it uploads all existing STL and session data. Later Restic snapshots deduplicate unchanged data.
4. Monitor and maintain backups
Inspect the next scheduled run and the last service result:
systemctl list-timers dragons-stash-backup.timer
systemctl status dragons-stash-backup.timer
systemctl status dragons-stash-backup.service
sudo journalctl -u dragons-stash-backup.service -n 100 --no-pager
sudo systemctl --failed
List the snapshots through the configured backup container:
./scripts/backup/restore.sh list
Check a selected repository snapshot before restoring it:
./scripts/backup/restore.sh verify SNAPSHOT_ID
verify runs restic snapshots SNAPSHOT_ID and restic check. Routine backup
runs apply retention with restic forget --keep-daily "$BACKUP_RETENTION_DAYS" --prune; with the example configuration, this retains 30 daily snapshots.
Choose BACKUP_RETENTION_DAYS based on storage capacity and the recovery
window you need. Watch Synology capacity and investigate any failed timer or
service promptly.
5. Restore modes
Run restore commands from the production Compose checkout after loading the
same backup environment used by systemd (for example, as root with
/etc/dragons-stash/backup.env exported). All restore staging directories must
be children of BACKUP_STAGING_PATH.
| Mode | Command | Effect |
|---|---|---|
| List snapshots | ./scripts/backup/restore.sh list |
Lists available Restic snapshots. Does not stop services or change volumes. |
| Verify a snapshot | ./scripts/backup/restore.sh verify SNAPSHOT_ID |
Confirms the snapshot exists and runs a Restic repository check. Does not change live data. |
| Restore to staging | ./scripts/backup/restore.sh restore-to-staging SNAPSHOT_ID STAGING_DIR |
Restores and validates a snapshot in a new or empty child directory of BACKUP_STAGING_PATH. Does not stop services or change volumes. |
| Replace live data | ./scripts/backup/restore.sh restore-live SNAPSHOT_ID --confirm-replace-live-data |
Stops application services and replaces the PostgreSQL database plus all protected volumes after validation. |
restore-live is destructive. It requires the exact
--confirm-replace-live-data flag and should be used only after a successful
staging restore has been inspected. It creates a safety database dump and
archives of the current protected volumes in local staging before replacement.
If a live restore fails, it leaves the application services stopped, retains the
safety artifacts and staging directory, and attempts rollback after replacement
has begun. Review the reported paths and service health before manually
starting services.
For a non-destructive recovery rehearsal, choose a snapshot from list and
restore it to a fresh staging directory, for example:
./scripts/backup/restore.sh restore-to-staging SNAPSHOT_ID /var/lib/dragons-stash/backup-staging/rehearsal-SNAPSHOT_ID
The restored tree must contain a non-empty PostgreSQL dump, its manifest, uploads, worker TDLib state, and bot TDLib state. Live restore additionally checks every retained manual-upload file reference against the staged uploads before replacing any live volume.