Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Backup Manager Laravel Package

nyoncode/laravel-backup-manager

Laravel Backup Manager is a Laravel package for organizing application backups in one place. It focuses on simplifying backup configuration, running and monitoring backup tasks, and integrating scheduled database and file backups into familiar Laravel workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish config, then open config/backup.php and find the profiles array — this is where nearly everything you will touch lives.
  2. Give each profile an explicit name, schedule, destination (disk + path), and retention block. Note that path defaults to backups for every profile, so sharing a disk between profiles is the expected arrangement, not an edge case.
  3. Run php artisan backup:run --profile=default, then php artisan backup:list --profile=default. Confirm the listing shows only that profile's backups — as of v1.3.0 it is scoped by the profile recorded in each backup's manifest.
  4. First real use case: a nightly default profile on the s3 disk with keep_latest = 7, plus a fortnightly full profile on the same disk with keep_latest = 5. Before v1.3.0 this pairing silently destroyed the full backups; you can now rely on it.

Where to look first when something is off: backup:list --profile=X (what the profile thinks it owns), then the manifest inside the archive, then CleanupReport.

Implementation Patterns

One profile per cadence, one destination per profile. The natural setup is default (nightly, short retention) and full (weekly/fortnightly, long retention) on the same disk. Retention is now counted per profile from the manifest tag, so the two policies no longer fight. If you want a shared blob pool (deduplicated snapshots), keep it — reference counting is per profile, and cleanups only delete what their own profile's keep-set releases.

Queue both halves of the lifecycle. BACKUP_QUEUE=true now dispatches CleanBackupsJob for backup:clean exactly as it does for backup:run. Schedule both without --sync so neither the backup nor the reference-counting cleanup runs inside the scheduler's process:

$schedule->command('backup:run --profile=default')->dailyAt('01:00');
$schedule->command('backup:clean --profile=default')->dailyAt('02:00');

Use --sync only for small destinations where you want the exit code inline (CI smoke tests, deploy hooks). --dry-run never queues — its product is the printed preview, so it always runs inline; that makes it safe and cheap to run from a tinker session or a cron on the web box.

Tune restore timeouts separately. Restores download, decrypt, unpack, and take a pre-restore snapshot, so they routinely outlast the backup job. Set queue.restore_timeout (BACKUP_QUEUE_RESTORE_TIMEOUT) to something generous — often 2–4× queue.timeout. Leaving it null inherits queue.timeout, which is the usual cause of a restore job being killed midway.

Call CleanupManager instead of reimplementing retention. The CLI, the queue, the dashboard, and the MCP tool all route through it now. If you have custom commands, health checks, or a "prune before deploy" step, resolve CleanupManager and apply the profile's policy there rather than hand-rolling a list-and-delete. You get the same two-engine sequence and a CleanupReport per destination that you can log or assert on.

Use StoredBackup::$profile / belongsTo() in your own tooling. Anything that walks a destination — a custom dashboard widget, an audit command, a Slack notifier — should filter with belongsTo($profile) rather than by path alone. This is the same predicate retention and listings use, so your reporting cannot drift from what actually gets deleted.

If you ship a custom SnapshotDriver, update the signature. applyRetention() now takes an optional trailing ?string $profile. Existing call sites keep working; your implementation must accept the parameter.

CI matrix with Pest. pestphp/pest is ^4.0|^5.0. Pest 5 requires PHP 8.4 + Symfony 8, so a Laravel 12 job stays on Pest 4 and only a Laravel 13 job picks up Pest 5. Pin per matrix leg rather than letting the resolver choose.

Gotchas and Tips

  • Upgrade behaviour is deliberately conservative, and that can surprise you. Backups whose manifest predates the profile tag (or cannot be read) still count as the asking profile's. So immediately after upgrading, a legacy backup sitting on a shared disk is visible to and deletable by both profiles. Run backup:clean --dry-run per profile first and look at the overlap; if it's not what you want, prune the legacy set once, manually, before letting scheduled cleanups run.
  • null still means "every backup in the path". RetentionManager::apply() and applyRetention() treat a null/omitted $profile as the old global behaviour. That is what keeps existing calls working — and it is exactly the footgun the release fixed. Never let $profile be null by accident in new code; assert it or default it explicitly to 'default'.
  • "My backup disappeared from the list" is usually correct. backup:list, backup:verify, and backup:restore no longer show a neighbouring profile's backups on a shared destination. If a restore can't find a file you can see on the disk, you're on the wrong profile — pass --profile.
  • Empty queue.queue now falls back to backups. Previously it dispatched onto a queue literally named '', which no worker consumes: jobs vanished with no failure record. If you were relying on the default connection's queue name, verify your worker listens on backups (or set queue.queue explicitly) after upgrading.
  • Queued cleanups are invisible until they aren't. The old inline behaviour hid failures in the scheduler's process; queued jobs at least land in failed_jobs. Watch that table and your queue dashboard after enabling BACKUP_QUEUE — a failing cleanup is silent in the backup log.
  • --dry-run and --sync are orthogonal. --dry-run forces inline regardless of BACKUP_QUEUE; --sync forces inline for real runs. Mixing BACKUP_QUEUE=true with a scheduled backup:clean and no --sync is the intended production shape.
  • Debugging order for a retention complaint: CleanupReport for the destination → StoredBackup::belongsTo() in tinker against the actual manifest → confirm the profile name in config matches the one passed on the CLI (they are matched exactly, not normalised).
  • Manifest read failures fail "safe" toward deletion-by-the-wrong-profile. An unreadable manifest is treated as belonging to the asking profile, so it will be pruned if it falls outside that profile's keep-set. If you care about a destination, keep manifests readable; consider backup:verify on a schedule.
  • Extension point: CleanupManager is the seam for anything retention-adjacent — custom reporting, alerting on CleanupReport, pre-prune hooks. Prefer it over subclassing RetentionManager, whose optional-profile parameter is now part of the contract.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core