marekmiklusek/database-backup
A Laravel package for creating database backups with Artisan commands and scheduler support. Configure where dumps are stored, automate regular backups, and keep your application data protected with minimal setup.
This release is gated on PHP 8.4+ and Laravel 13. If your app is on Laravel 11 or 12 you cannot upgrade — pin the previous major and treat the security fixes below as unpatched risk in your own environment (restrict the backup directory, or upgrade the app first). Check composer.json constraints before anything else; the package now pulls individual illuminate/* components rather than laravel/framework, so a composer update no longer silently drags your framework version around.
Minimal path from zero to a working nightly backup:
composer require the package, then publish the config: php artisan vendor:publish --tag=<config-tag> (see the README for the exact tag; the class now exposes its config name as a constant rather than a container binding).storage.disk to the disk where the .sql dump lands. If you use Google Drive as the destination, either keep the credentials disk named google or set the new storage.google_disk key to whatever you actually call it.The first use case to wire up is the obvious one: nightly mysqldump to a local disk plus a Google Drive copy, with retention keeping N days. Once that is running, add failure notifications before you add retention — a silent failure is worse than a full disk.
Upgrading checklist: run php artisan config:clear and re-cache. Config is now merged in register() instead of boot(), which means stale cached config from the old version can produce confusing half-applied state. Then verify storage.google_disk and, if you have use_both_disks enabled with a local primary disk, expect it to start working for the first time — see the gotchas.
Scheduling. Register the backup command in routes/console.php or Kernel::schedule() at an hour that does not collide with your other cron work, and chain withoutOverlapping() — the credentials-file collision fixed in this release was a real concurrency bug, but overlapping backups still compete for disk and Drive quota. onOneServer() if you run multiple app servers against the same database.
Local-then-cloud with use_both_disks. This is the pattern the flag exists for: storage.disk names your local disk, storage.google_disk names the Drive disk. Before this release that combination was broken and threw Config key [client_id] not found; it now works. If you had a workaround in place — repointing storage.disk at the Google disk, or duplicating the Drive credentials onto the local disk — remove it, or you will end up with the credentials configured in two places and only one of them maintained.
Retention. Configure the retention window explicitly rather than relying on the default. The behavioural contract after this release is narrower than before: cleanup only removes files matching the package's own {prefix}_{date}.sql pattern. That is the fix (it used to delete anything in the directory), but it also means any file you renamed, hand-placed, or generated with a different prefix is now permanent. If you post-process dumps into .sql.gz with your own script, that script owns the cleanup.
Failure handling. Wrap invocations in your own try/catch only if you need to do something the package's failure notification does not — e.g. alert PagerDuty or write an audit row. The package's own cleanup of the temporary credentials file is now in a finally block, so exceptions no longer leak the file; do not add your own temp-file management around it.
Integration tips.
prefix so retention does not cross-delete between them. The {prefix}_{date}.sql matcher means prefixes must actually differ.The credentials file is now 0600 — and that can break things. The file is created empty, chmodded to 0600, then written. If the process that runs mysqldump is a different user from the one that runs your consumer (e.g. queue worker vs. web user, or a supervisor-configured cron user), the write will now fail where it previously succeeded on 0644. Symptom: permission-denied errors that only appear on the scheduler, never when you run the command by hand in your shell. Fix by aligning users, not by loosening the permission.
Concurrent-safe temp files mean you cannot predict the path. Each run gets a unique filename. Any monitoring, log-scraping, or lsof-style debugging that assumed a fixed name needs updating.
Retention uses filename dates, falling back to mtime — not ctime. Previous versions dated by ctime, which moves on chmod, chown, and rsync. That means environments where an rsync or a permissions sweep ran over the directory had retention silently stop, and old backups accumulated until the disk filled. After the upgrade, expect the first cleanup run to delete a large backlog at once. Check disk usage and run one cleanup manually off-hours before letting cron do it, or you will see a suspicious mass-deletion event in your storage metrics.
Cleanup no longer touches foreign files. If you were relying — accidentally — on the package emptying the backup directory, you now have to manage your own artifacts. Conversely, if you had a paranoid check for "cleanup deleted my unrelated file", you can drop it.
use_both_disks with a local primary disk starts working on upgrade. Treat this as a behaviour change, not just a bug fix: setups that were silently failing may now begin uploading to Drive for the first time, consuming quota and bandwidth. Audit your Drive storage before you deploy, and confirm storage.google_disk points at the disk that actually holds the credentials if you ever renamed it from google. The default is google, so untouched setups need no change.
Config merges in register(). Package defaults are now available outside the console — useful for reading config in a web request or a health-check route — but it also means a cached config file from the old version is actively wrong after upgrading. Always config:clear before config:cache during deploy.
The configName container binding is gone. It is a class constant now. If any of your code did app('configName') or similar, it fails at resolve time. Rare, but it is a genuine break beyond the PHP/Laravel bump.
Debugging a backup that produces nothing. Order of checks: (1) can the scheduler user read the DB credentials and write to storage.disk; (2) does the target disk resolve with the right config — this is where the old use_both_disks bug hid; (3) is mysqldump on the PATH of the cron user, not your interactive shell; (4) did a notification fire? Failure notifications were documented only in this release, so a pre-existing install may never have been told about failures at all. Configure them.
Extension points. The config file is the only real extension surface: disk names, prefix, retention, notification recipients. There is no plugin system, and the new static-analysis/Rector/Pint tooling applies to the package's own source, not yours — do not expect those configs to be publishable into your app.
How can I help you explore Laravel packages today?