Repository navigation
feat(backups): settings-driven database backups that fail loudly - #288
Merged
Merged
Conversation
Replace the db:backup command, which piped mysqldump through gzip without pipefail, swallowed upload errors and returned success on a failed dump. That is how production uploaded 20-byte empty dumps on 2026-09-24/25 (no mysqldump in the image) and then nothing at all while reporting DONE. - DatabaseBackupService streams the dump client's stdout into gzip in PHP (no shell pipeline), passes the password via MYSQL_PWD, and fails a run on a non-zero exit, a missing '-- Dump completed' marker, a dump under min_size_bytes, or an uploaded object whose size differs from the file. - Uploads go to any filesystem disk (bucket override for s3, key prefix); retention by age and/or count runs only after a fully successful run and always keeps each database's newest backup. - Every attempt is recorded in database_backups (status, size, duration, error, trigger); failures can email configured addresses. - Settings live in system.database-backups (env defaults in config/database-backups.php) and drive the schedule; disabled by default. - Admin endpoints under int/v1/database-backups: settings get/save/reset, recent runs, and a queued 'run now'. - db:backup exits non-zero on any failure; --force runs while disabled.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The
fleetbase-db-backupsbucket only ever received four 20-byte empty gzip files, written on 2026-09-24/25 by a local dev stack's scheduler. Production's scheduler has rundb:backupnightly and logged DONE without writing anything. The old command made every one of these failures invisible:mysqldump … | gzip > filewithout pipefail. Neither image hasmysqldump, so gzip compressed empty input into 20 bytes and exited 0.MultipartUploadExceptionand only mentioned it in verbose mode. Production's task role can'tPutObjectto the bucket, and nobody saw that.return;with exit code 0.What
DatabaseBackupServiceMYSQL_PWD, not on the command line.-- Dump completedmarker,min_size_bytes, orDatabaseBackupFailed) and writesLog::error.database_backupstable andDatabaseBackupmodel: one row per database per run, with status, trigger, disk/path, size, duration, error and when it was pruned. Rows are pruned after a year.DatabaseBackupSettings:config/database-backups.php(DB_BACKUP_*); the admin override is stored assystem.database-backups.DatabaseBackupSettings::schedule()registersdb:backup --trigger=scheduledon the configured cron (UTC), and only while enabled.db:backup:--forceruns it while backups are disabled.--connection=*limits it to specific connections.--trigger=records what started the run.DatabaseBackupController(AdminRequest), underint/v1/database-backups:GET/POST/DELETE settingsGET runs?limit=(newest first)POST run(202, queuesRunDatabaseBackup)MysqlS3Backup,S3BackupTrimmerandconfig/laravel-mysql-s3-backup.php.Deploy notes
default-mysql-client(MariaDBmysqldump, which works against MySQL 8.0).fleetbase/internalsstill schedulesdb:backupdaily. With this change it would be a no-op while backups are disabled, but it must be removed to avoid a second daily run once they're enabled. There is a companion PR.task-92b1ceb) has onlys3:ListBucketonfleetbase-db-backups. It needss3:PutObject,s3:GetObjectands3:DeleteObjectonarn:aws:s3:::fleetbase-db-backups/*. See the runbook in the fleetbase/fleetbase PR.DB_BACKUP_ENABLED=true) and pick thes3disk with bucketfleetbase-db-backups.Tests
tests/Unit/DatabaseBackupsTest.phpruns the real service with a PHP one-liner standing in formysqldump, so streaming, compression, the completion-marker and size checks, upload, retention, locking and notification all execute for real against a local disk. It also covers the command, the job, the notification, the model, the controller (including validation) and the route contract.Not run locally (per the repo owner's no-local-builds rule); CI is the verification.