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

Tomato Backup Module Laravel Package

tomatophp/tomato-backup-module

A backup management module for TomatoPHP that lets you create, schedule, and restore database and file backups from your Laravel admin panel. Integrates with the TomatoPHP ecosystem for quick setup and a simple UI to manage backup jobs.

View on GitHub
Deep Wiki
Context7

Getting Started

Prereqs: Laravel 10/11, PHP 8.1+, Filament 3, and ideally the TomatoPHP admin stack (tomato-admin, tomato-settings). The whole value of this package is the Filament surface — on an API-only or non-Filament app, install spatie/laravel-backup directly instead.

Install (pin the version — v1.0.0 shipped with an empty changelog):

composer require tomatophp/tomato-backup-module:1.0.0
php artisan vendor:publish --tag=tomato-backup-config
php artisan vendor:publish --tag=tomato-backup-migrations
php artisan migrate

Also publish Spatie's config if the module doesn't already do it — you will be editing it:

php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"

Register the Filament plugin in your panel provider (check src/ for the exact class name — it follows the TomatoBackupPlugin::make() convention like other TomatoPHP modules):

->plugin(\TomatoPHP\TomatoBackup\TomatoBackupPlugin::make())

Then clear caches — Filament caches plugin/nav state and will happily show nothing:

php artisan optimize:clear

First use case, in this order:

  1. Open the panel, hit the manual backup action, confirm a .zip lands in storage/app/backups (or your configured disk).
  2. php artisan backup:list — confirm the artifact is registered with a sane size.
  3. Restore it into a scratch database before you do anything else. See the gotcha below; this is the only step that proves the package works.

Implementation Patterns

Treat Spatie config as the source of truth. The module is a UI + scheduling layer over config/backup.php, backup:run, backup:clean, and backup:monitor. Keep your retention, destination, and include/exclude rules in that config file, and use the module only for triggers and visibility. If the module's abstractions fight you, delete the wrapper and keep the config — the escape hatch costs one scheduled task.

Split database and file backups. One nightly --only-db job is cheap; a files backup of public/ and storage/app is not. Run them on different schedules:

Schedule::command('backup:run --only-db')->dailyAt('01:30')->withoutOverlapping()->onOneServer();
Schedule::command('backup:run --only-files')->weeklyOn(0, '03:00')->withoutOverlapping()->onOneServer();
Schedule::command('backup:clean')->dailyAt('04:00');
Schedule::command('backup:monitor')->dailyAt('05:00');

Destination patterns. Create a dedicated disk rather than reusing your app's s3 disk, so backup credentials can be scoped and rotated independently:

// config/filesystems.php
'backups' => [
    'driver' => 's3',
    'key' => env('BACKUP_AWS_KEY'),
    'secret' => env('BACKUP_AWS_SECRET'),
    'bucket' => env('BACKUP_AWS_BUCKET'),
    'region' => env('BACKUP_AWS_REGION'),
    'endpoint' => env('BACKUP_AWS_ENDPOINT'),   // MinIO / R2 / Spaces
    'use_path_style_endpoint' => env('BACKUP_AWS_PATH_STYLE', false),
],

Then point backup.disks.backups.driver at backups. For S3-compatible providers, forgetting use_path_style_endpoint and the visibility setting is the usual cause of "backup ran but nothing is in the bucket."

Wire alerting, don't assume it exists. backup:monitor only notifies if you configure monitor_backups[].notifications and the notifiable address. For anything you actually depend on, add your own failure surface:

Schedule::command('backup:run --only-db')
    ->dailyAt('01:30')
    ->onFailure(fn () => Log::critical('backup:run failed'))
    ->onSuccess(fn () => Cache::put('backup:last_success', now(), now()->addDays(7)));

Expose backup:last_success age in a Filament widget — last-successful-backup age is the health signal that catches silent non-execution.

Restore is a process, not a button. Spatie does not ship a restore command; a backup is a zip containing a .sql dump plus a db-dumps/files tree. The module's "restore" is a download/assist surface. Wrap the real thing in your own command so it is repeatable and never relies on someone reading a wiki at 3am:

unzip -o backup.zip -d /tmp/restore
mysql -h $DB_HOST -u $DB_USER -p $DB_NAME < /tmp/restore/db-dumps/mysql-*.sql

Per-tenant backups in a multi-tenant panel. There is no built-in tenancy scoping — the module lists one backups disk. If you need per-tenant archives, schedule a job that iterates tenants, calls backup:run with a tenant-scoped database.connections.* config, and writes to a per-tenant disk prefix. Budget for this; don't expect the UI to do it.

Non-production reset workflow. For dev/staging, a scheduled backup:run --only-db against production (read replica) plus a one-line import script gives you a refreshable environment for free — this is a genuinely useful day-to-day win and cheaper than a dedicated tool.

Gotchas and Tips

The v1.0.0 release notes are a changelog link with nothing in them. No migration notes, no breaking-change list. Pin 1.0.0 exactly, read the small src/ tree before adopting, and don't float a caret until you have a couple of release cycles of evidence.

Silent non-execution is the dominant failure mode. Check three things before trusting the schedule: the scheduler is actually running (cron or schedule:work), a queue worker exists in every environment (backup:run is queued by default; database queue with no worker = backups that never fire), and onOneServer() has a shared cache driver (redis/database) — it silently doesn't dedupe on file or array.

Silent partial success. The dump succeeds, the upload fails, and the record still looks healthy. Verify the package surfaces upload failures explicitly; if not, assert on artifact existence and size in your own post-run check (backup:list output or a disk exists() probe), not just on exit code.

Retention defaults will bite you. Spatie's default cleanup strategy deletes oldest backups when the destination exceeds a size cap — on a small dev bucket that can wipe your entire history. Set cleanup.default_strategy.* deliberately, and never run backup:clean concurrently with backup:run.

Temp disk exhaustion. Large dumps stage in backup.temporary_directory before upload. A 20GB dump on a container with 10GB free fails mid-run with a confusing error. For anything non-trivial, use --only-db, schedule off-peak, and raise the job timeout (backup:run respects the queued job timeout, and CLI dumps are bounded by max_execution_time / set_time_limit).

mysqldump/pg_dump must exist in the container and match the server major version. Alpine images frequently lack them; MariaDB client against MySQL 8 (or vice versa) produces dumps that import with corrupt/partial schema. Add a startup check in your Dockerfile or a pre-flight in the scheduled task.

Encrypted backups are a one-way door. If you set backup.password, the archive is encrypted with it. Lose the password and you lose every backup, including the ones you were keeping for compliance. Store it in the secret manager, not in .env on the app server only.

Migrations may or may not be the module's. Check whether it publishes its own tables or reuses Spatie's monitor_backups schema; running both creates duplicate/conflicting tables. Confirm before migrate on any environment you care about.

Credentials leak through published config. config/backup.php and config/filesystems.php get committed with whatever you typed during the spike. Everything sensitive goes through env() with the value only in .env / the secret store.

Filament caching. After registering the plugin or changing its navigation, php artisan optimize:clear (or filament:optimize-clear) — "the menu item doesn't appear" is almost always this.

Extension points to reach for, in order: (1) the Filament resource pages for extra header actions ("backup now, DB only"), (2) a custom BackupDestination/disk for exotic storage, (3) your own commands wrapping backup:run. Don't fork the module to add a button — publish the view or override the resource.

Keep a manual fallback documented. The UI is community-maintained OSS with no SLA. Ensure any engineer on call can run php artisan backup:run --only-db and perform the unzip-and-import restore without the panel. Then rehearse it quarterly with a named owner — the untested restore is the failure that actually costs you.

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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect