backup-manager/laravel
Laravel Backup Manager lets you back up databases and files, store them on local or cloud disks, restore snapshots, and monitor backup health with notifications. A simple, reliable backup solution for Laravel applications.
Install the package and register it manually — it predates Laravel package auto-discovery, so this step is always required:
composer require backup-manager/laravel
// config/app.php
'providers' => [
BackupManager\Laravel\LaravelServiceProvider::class,
],
'aliases' => [
'BackupManager' => BackupManager\Laravel\LaravelFacade::class,
],
Publish the config and open config/backup-manager.php first — that file is the whole package:
php artisan vendor:publish
A minimal working configuration looks like this:
// config/backup-manager.php
return [
'database' => [
'connections' => [
'mysql' => [
'type' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'user' => env('DB_USERNAME'),
'pass' => env('DB_PASSWORD'),
'database' => env('DB_DATABASE'),
],
],
],
'storage' => [
'local' => [
'type' => 'Local',
'root' => storage_path('backups'),
],
],
'compression' => [
'gzip' => ['type' => 'Gzip'],
],
];
First use case — dump the configured MySQL connection to a gzipped local file:
use BackupManager\Filesystems\Destination;
app('backup-manager')
->makeBackup()
->run('mysql', [
new Destination('local', 'db-'.date('Ymd-His').'.sql'),
], 'gzip');
The three string/object arguments map directly to config keys: the database connection name, one or more Destination(storageName, path) objects, and the compression name. If those keys are not in config/backup-manager.php, you get an exception at runtime.
After the first manual run, check php artisan list for the bundled db:backup / db:restore commands, then look at vendor/backup-manager/backup-manager/src — the Manager, Databases, and Filesystems classes are where the real behaviour lives. The Laravel package is a thin service-provider wrapper around that core.
Wrap the manager in one application service. Do not scatter app('backup-manager') calls through controllers. Create a small BackupService that owns naming, destinations, compression, and logging:
class BackupService
{
public function dump(string $connection = 'mysql'): string
{
$file = sprintf('%s-%s.sql', $connection, now()->format('Ymd-His'));
app('backup-manager')->makeBackup()->run(
$connection,
[
new Destination('local', $file),
new Destination('s3', 'database/'.$file),
],
'gzip'
);
return $file;
}
}
Writing to multiple destinations in a single run() is the most useful day-to-day pattern: keep a local copy for fast restores and ship one to S3 for disaster recovery.
Schedule it, but wrap it. Use Laravel’s scheduler with overlap protection and a failure notification:
$schedule->call(function () {
app(BackupService::class)->dump('mysql');
})->dailyAt('02:00')->withoutOverlapping();
Keep backups out of web requests. The dump is executed through shell binaries and can exceed PHP’s max_execution_time; CLI/scheduler execution is the only reliable path.
Restore is a separate, explicit operation. The restore call reverses the arguments: storage name, file path, database connection, compression.
app('backup-manager')
->makeRestore()
->run('s3', 'database/db-20240101-020000.sql', 'mysql', 'gzip');
Treat restore as an operational task, not an application feature. Guard it by environment, require a confirmation flag in your custom command, and never expose it over HTTP.
Mirror Laravel’s database config instead of duplicating credentials. The package does not read config/database.php. Either keep the values in sync manually or build the backup config from Laravel’s connection array so hosts and passwords don’t drift:
$db = config('database.connections.mysql');
'database' => [
'connections' => [
'mysql' => [
'type' => 'mysql',
'host' => $db['host'],
'port' => $db['port'],
'user' => $db['username'],
'pass' => $db['password'],
'database' => $db['database'],
],
],
],
Add one connection per database. Multi-tenant or multi-database applications should register a backup connection for each database and loop over them. Do not try to reuse one connection key with different credentials.
Use timestamped, prefixed object keys. A path like database/{env}/{connection}/{date}.sql makes lifecycle rules on S3 straightforward and prevents production and staging backups from overwriting each other.
Implement retention yourself. The package creates backups; it does not rotate them. Add a cleanup step to the same scheduled job that deletes local files older than N days, and configure an S3 lifecycle policy for remote objects.
Use the local filesystem and SQLite in tests. Point the test config at a temporary SQLite file and a temporary local directory, then assert that the destination file exists and is non-empty. This exercises the full procedure without touching MySQL or external storage.
spatie/laravel-backup; use this package only where it is already established.dump_command_path or restore_command_path, the package concatenates the path with the binary name. Include the trailing slash (/usr/local/bin/) or the command will be malformed. This is the most common cause of “command not found” failures on MAMP/Homebrew setups.mysqldump, mysql, pg_dump, psql, etc. proc_open/exec must not be disabled, and the binaries must be on the PATH of the user running PHP-FPM or cron. Windows support is poor.mysqldump are visible to other users on the host. Prefer a restricted ~/.my.cnf or run backups as a dedicated user.php artisan config:cache bakes env() results into the cached config. Re-run config:cache after changing backup credentials, and remember that env() outside config files returns null when cached.root in the storage definition is the base directory/bucket prefix; the path in Destination is relative to it. For Local, make sure the root directory exists and is writable by the PHP/cron user.run() in try/catch, log the exception, and fail the scheduled job loudly. Silent backup failures are worse than no backups.--force in any custom restore command.types arrays in config/backup-manager.php, implementing the core package’s database/filesystem interfaces. Inspect the core source before extending — the Laravel wrapper itself adds little beyond config binding and commands.withoutOverlapping() or a lock file. A long-running dump plus a second scheduler tick can produce two concurrent dumps and corrupt or exhaust storage.How can I help you explore Laravel packages today?