Summary
backupDatabase() / buildBackupScript() write a timestamped gzipped dump to /var/backups/ts-cloud/databases/<name>-<stamp>.sql.gz on every call, but nothing ever prunes that directory. buildListBackupsScript() only lists it. So each dashboard "Backup" action (POST /api/databases/backup) or ts-cloud db:backup call leaves a full dump on the box indefinitely, with no cap and no off-box copy.
Verified against @stacksjs/ts-cloud@0.16.15; same code in the current feat/host-cleanup-timer source.
Where
packages/ts-cloud/src/deploy/dashboard-database.ts:
DB_BACKUP_DIR = '/var/backups/ts-cloud/databases'buildBackupScript()dumps${name}-$STAMP.sql.gzand never trims the dirbuildListBackupsScript()isls -1t ${dir}/*.sql.gz | head -50 | sed …(list only, no delete)backupDatabase()calls the above and is wired to the dashboard handler
Impact
For a large database this slowly fills the disk. The scheduled path (drivers/shared/backups.ts) correctly applies retention: { count, maxAge } via ts-backups; this on-demand path has no retention at all, so it is an unbounded local-accumulation vector. (Context: we recently had to hand-clean a shared box where a full database's dumps had grown to ~100G; a dashboard backup button with no cap has the same shape, just manually triggered.)
Suggested fix
After a successful dump, keep the newest N per database and/or delete older than M days, mirroring ComputeBackupConfig.retentionCount / retentionDays that the scheduled path already honours. Or route on-demand dumps through ts-backups so dump + retention + off-box upload are consistent across both paths.