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.
Architecture fit. This is a conventional Laravel package occupying the backup/DR lane: config-file driven, scheduled via the console kernel, shelling out to mysqldump, writing .sql artifacts to a Laravel filesystem disk, with optional secondary upload (Google Drive) and email notification. It has no request-path coupling, which is the ideal shape for a dependency like this — a failure domain we can isolate, alert on, and replace. The two implicit infrastructure assumptions are (a) a mysqldump binary of adequate version on every host, and (b) a writable local filesystem with sane permissions — both of which the security section shows were previously mishandled.
Integration feasibility. Dominated by one constraint: PHP 8.4+ and Laravel 13 are now mandatory. If our application is on Laravel 11/12, v2 is simply not installable, and the security fixes are unavailable to us until the platform upgrade lands. Secondary consideration: swapping laravel/framework for individual illuminate/* components is architecturally better (it stops forcing a framework version and reduces Composer constraint conflicts across a mixed fleet), but it must be resolution-tested against our lockfile, since illuminate constraints can collide with other packages pinning framework internals.
Technical risk.
0644. On any shared host, container with a shared volume, or multi-tenant VM, the database password has been readable by other local users. This is a possible exposure event, not merely a defect; scope it and consider credential rotation.exec() rather than in finally means thrown exceptions left the secret on disk. Sweep temp directories for residue.ctime (mutated by chmod, chown, mv, rsync) meant cleanup stopped pruning after routine ops, and the disk filled. This is an availability-class failure that presents as a backup problem. Also note the prior behavior: cleanup deleted everything in the directory. If that directory ever held non-package files, we may already have lost them. Audit before assuming the fix is purely forward-looking.use_both_disks never worked with a local primary disk. Any team that trusted the documented configuration had no offsite copy while believing they had one. This is the highest-severity item in the release because it is a false-assurance failure: it invalidates past confidence in the DR posture, not just the code path. Treat as an evidence-gathering exercise (Drive inventory, run logs, artifact ages) and a governance finding.{prefix}_{date}.sql. Safer, but it means renamed, moved, legacy-named or differently-prefixed artifacts will never be pruned — silent orphan accumulation replacing silent over-deletion. Requires a one-time manual sweep and a size/file-count alert.max, Rector, Pint, and a CI matrix across PHP 8.4/8.5 and lowest/highest dependencies. That is a genuine maintainability upgrade and materially lowers our third-party risk. Caveats: it is a single release, 100% coverage does not prove correctness of shell-out/exit-code handling, and the claim needs re-verification each release.configName container binding → class constant, and config merged in register() instead of boot(). The latter is an improvement (defaults available in queue/HTTP contexts) but must be validated under config:cache; the former is an internal API break if any local code resolved it from the container.minimum-stability: stable — fine, removes a resolution surprise.Key questions.
0700 tmpdir, serialized cron)?use_both_disks failed silently in our environments — what logs, artifact inventories, or Drive listings can prove it?{prefix}_{date}.sql exactly (prefix, timezone, format), or will part of our backlog be permanently unprunable?Stack fit. Standard Laravel package, no infrastructure beyond mysqldump and a writable disk; offsite is optional via a Drive-configured filesystem disk. Because it is scheduler-driven and out-of-band, it can be adopted, pinned, or replaced without touching application code — a low-blast-radius dependency if we treat it as an operational component rather than a library.
Migration path. v1 → v2 is not an independent workstream; it is a dependent of the PHP 8.4 / Laravel 13 platform upgrade. Plan it as a trailing step in that effort. If the framework upgrade is more than one quarter out, do not leave the security issues open: pin v1 with an explicit, time-boxed risk acceptance plus compensating controls (restrict the package to a dedicated OS user, run backups serially to avoid the filename race, harden/relocate the temp directory and monitor it, and manually verify offsite copies exist).
Compatibility checks.
illuminate/* constraints — install in a branch, don't hand-wave it.php artisan config:cache with config merged at register(); a boot failure here is a production outage.storage.google_disk if our Drive disk is not named google; and confirm storage.disk intent for each environment.configName container resolution in any local extensions or override packages.Sequencing.
configName.storage.google_disk explicitly rather than relying on the default.0600), force an exception (bad host/credentials) and confirm no residue, and launch two concurrent runs to confirm distinct temp files.{prefix}_{date}.sql is removed; touch a file's ctime via chmod/mv and confirm it is still pruned.Rollback. Artifact format is unchanged, so downgrading does not orphan data. Keep a v1 branch pinned and tested. Note that rollback reintroduces the three security defects — acceptable only as a short-lived move with compensating controls in place.
Maintenance. Materially improved: a real test suite, max-level static analysis, opinionated formatters, and a CI matrix across PHP 8.4/8.5 and dependency extremes. That moves this from "unverified third-party code we hope works" to "a dependency we can reason about." Two structural concerns remain: the strict PHP 8.4+ / Laravel 13 floor couples our upgrade clock to the maintainer's release cadence (we now inherit forced upgrades), and the maintainer's bus factor is unproven. Budget a small amount of recurring effort to spot-check coverage/CI claims per release rather than assuming the one-time quality jump persists.
Support. Expect a narrow window — latest Laravel only. Get the policy in writing. If we ever need support for a Laravel version they drop, we own a fork, and we should know that before we depend on it in a DR-critical path.
Scaling. mysqldump is per-node, I/O-heavy, and lock-holding. The unique-credentials fix removes a genuine correctness race, so overlapping cron slots and parallel app nodes are now safe on the credential dimension — but two concurrent dumps of the same database still double the load and lock contention. For large databases: off-peak windows, dump from a replica where possible, cap concurrency, and monitor dump duration as a leading indicator. Retention is a directory scan whose cost grows with file count, which is another reason to keep the artifact count bounded.
Failure modes to instrument.
mysqldump absence or version drift and ignored non-zero exits — assert exit status and route notifications (the README now documents email settings; configure them and point them at a monitored channel, not a personal inbox).register() merge is low risk but high blast radius; validate with cached config in staging.Ramp-up. Low for engineers: config file plus a scheduled command. The real onboarding cost is operational, and it should be written down — a runbook covering where artifacts land, how retention decides what to delete, how to verify a backup is restorable, what a failed run looks like, and who is paged. Indicative effort: 0.5–1 day for the package upgrade and behavioral validation; 1–3 days for the audit, orphan sweep, and restore drill; the framework upgrade is its own much larger item. Assign a single named owner for backup integrity and commit to a recurring restore test with recorded results — the most important lesson from this release is that "configured" and "working" were not the same thing.
How can I help you explore Laravel packages today?