Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/cleaning-up-old-backups/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ You can clean up your backups by running:
php artisan backup:clean
```

If you want to clean a backup with a specific configuration with a specific configuration, run:
```bash
php artisan backup:clean --config=backup
```

We'll tell you right off the bat that the package by default will never delete the latest backup regardless of its size or age.

## Determining which backups should be deleted
Expand Down
27 changes: 26 additions & 1 deletion docs/taking-backups/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,32 @@ This section of the configuration determines which files and databases will be b
The specified databases will be dumped and, together with the selected files, zipped. The zip file will be named`<specified name in configuration>/<Y-m-d-H-i-s>.zip`.

The more files you need to backup, the bigger the zip will become. Make sure there's enough free space on your disk to create the zip file. After the source zip file has been copied to all destinations, it will be deleted.


### Running backups with a specific configuration
If you want to back up different areas of your Laravel application separately – for example with different schedules, database connections, filesystem disks, or cleanup settings – you can create custom backup configuration files.

#### Example:
Additional config files placed in the config/ directory:

- config/backup_database.php
- config/backup_invoices.php
- config/backup_uploads.php

You can then run backups and cleanup commands individually:

```bash
php artisan backup:run --config=backup_database
php artisan backup:clean --config=backup_database

php artisan backup:run --config=backup_invoices
php artisan backup:clean --config=backup_invoices

php artisan backup:run --config=backup_uploads
php artisan backup:clean --config=backup_uploads
```

This allows full flexibility in scheduling, retention, and target destinations for each backup scope.

### Determining the destination of the backup

The zipped backup can be copied to one or more filesystems. This section of the configuration is where you specify those destination filesystems.
Expand Down
10 changes: 7 additions & 3 deletions src/Commands/BackupCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class BackupCommand extends BaseCommand implements Isolatable
{
use Retryable;

protected $signature = 'backup:run {--filename=} {--only-db} {--db-name=*} {--only-files} {--only-to-disk=} {--disable-notifications} {--timeout=} {--tries=}';
protected $signature = 'backup:run {--filename=} {--only-db} {--db-name=*} {--only-files} {--only-to-disk=} {--disable-notifications} {--timeout=} {--tries=} {--config=}';

protected $description = 'Run the backup.';

Expand All @@ -34,6 +34,10 @@ public function handle(): int
set_time_limit((int) $this->option('timeout'));
}

if ($this->option('config')) {
$this->config = Config::fromArray(config($this->option('config') ?? 'backup'));
}

try {
$this->guardAgainstInvalidOptions();

Expand Down Expand Up @@ -92,8 +96,8 @@ public function handle(): int
if (! $disableNotifications) {
event(
$exception instanceof BackupFailed
? new BackupHasFailed($exception->getPrevious(), $exception->backupDestination)
: new BackupHasFailed($exception)
? new BackupHasFailed($exception->getPrevious(), $exception->backupDestination)
: new BackupHasFailed($exception)
);
}

Expand Down
6 changes: 5 additions & 1 deletion src/Commands/CleanupCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class CleanupCommand extends BaseCommand implements Isolatable
use Retryable;

/** @var string */
protected $signature = 'backup:clean {--disable-notifications} {--tries=}';
protected $signature = 'backup:clean {--disable-notifications} {--tries=} {--config=}';

/** @var string */
protected $description = 'Remove all backups older than specified number of days in config.';
Expand All @@ -36,6 +36,10 @@ public function handle(): int

$this->setTries('cleanup');

if ($this->option('config')) {
$this->config = Config::fromArray(config($this->option('config') ?? 'backup'));
}

try {
$backupDestinations = BackupDestinationFactory::createFromArray($this->config);

Expand Down
26 changes: 26 additions & 0 deletions tests/Commands/BackupCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,29 @@

Storage::disk('local')->assertExists($this->expectedZipPath);
});

it('can backup with runtime changed configuration', function () {
$this->date = Carbon::create('2025', 8, 1, 10, 1, 1);
Carbon::setTestNow($this->date);

config()->set('backup.backup.destination.filename_prefix', 'prefix1_');
$this->expectedZipPath = 'mysite/prefix1_2025-08-01-10-01-01.zip';
$this->artisan('backup:run', ['--only-files' => true])->assertExitCode(0);

Storage::disk('local')->assertExists($this->expectedZipPath);
Storage::disk('secondLocal')->assertExists($this->expectedZipPath);

// Now change the configuration
config()->set('backup.backup.destination.filename_prefix', 'prefix2_');
$this->expectedZipPath = 'mysite/prefix2_2025-08-01-10-01-01.zip';

// Run again without the config option, files should not exist
$this->artisan('backup:run', ['--only-files' => true])->assertExitCode(0);
Storage::disk('local')->assertMissing($this->expectedZipPath);
Storage::disk('secondLocal')->assertMissing($this->expectedZipPath);

// Run again with specified configuration, backup should be created
$this->artisan('backup:run', ['--only-files' => true, '--config' => 'backup'])->assertExitCode(0);
Storage::disk('local')->assertExists($this->expectedZipPath);
Storage::disk('secondLocal')->assertExists($this->expectedZipPath);
});