amdadulhaq / database-backup-laravel
Simple database backups for Laravel (PostgreSQL, MySQL, MariaDB, SQLite) streamed to any filesystem disk, including S3-compatible APIs like Cloudflare R2.
Package info
github.com/amdad121/database-backup-laravel
pkg:composer/amdadulhaq/database-backup-laravel
Fund package maintenance!
Requires
- php: ^8.2|^8.3|^8.4|^8.5
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/filesystem: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- symfony/process: ^7.0|^8.0
Requires (Dev)
- driftingly/rector-laravel: ^2.0
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
README
Simple database backups for Laravel. Dumps one PostgreSQL or MySQL / MariaDB connection (SQLite also supported) and stores it on any Laravel filesystem disk — the local disk by default, or an S3-compatible API such as Cloudflare R2, MinIO, DigitalOcean Spaces, Backblaze B2 or AWS S3.
Requirements
- PHP 8.2, 8.3, 8.4, or 8.5
- Laravel 11, 12, or 13
pg_dumpfor PostgreSQL,mysqldumpfor MySQL/MariaDB (SQLite needs neither)league/flysystem-aws-s3-v3when the destination disk is an S3 disk
Installation
You can install the package via composer:
composer require amdadulhaq/database-backup-laravel
Publish the config file:
php artisan vendor:publish --tag=database-backup-config
The service provider is auto-discovered.
Destination disk
Any disk from config/filesystems.php works. Point DB_BACKUP_DISK at it.
AWS S3
// config/filesystems.php 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), ],
DB_BACKUP_DISK=s3
Cloudflare R2
'r2' => [ 'driver' => 's3', 'key' => env('R2_ACCESS_KEY_ID'), 'secret' => env('R2_SECRET_ACCESS_KEY'), 'region' => 'auto', 'bucket' => env('R2_BUCKET'), 'endpoint' => env('R2_ENDPOINT'), // https://<account-id>.r2.cloudflarestorage.com 'use_path_style_endpoint' => true, ],
DB_BACKUP_DISK=r2
MinIO
'minio' => [ 'driver' => 's3', 'key' => env('MINIO_ACCESS_KEY'), 'secret' => env('MINIO_SECRET_KEY'), 'region' => 'us-east-1', 'bucket' => env('MINIO_BUCKET'), 'endpoint' => env('MINIO_ENDPOINT'), // http://minio:9000 'use_path_style_endpoint' => true, ],
DigitalOcean Spaces
'spaces' => [ 'driver' => 's3', 'key' => env('DO_SPACES_KEY'), 'secret' => env('DO_SPACES_SECRET'), 'region' => env('DO_SPACES_REGION'), // e.g. nyc3 'bucket' => env('DO_SPACES_BUCKET'), 'endpoint' => env('DO_SPACES_ENDPOINT'), // https://nyc3.digitaloceanspaces.com ],
Backblaze B2 (S3-compatible)
'b2' => [ 'driver' => 's3', 'key' => env('B2_KEY_ID'), 'secret' => env('B2_APP_KEY'), 'region' => env('B2_REGION'), // e.g. us-west-004 'bucket' => env('B2_BUCKET'), 'endpoint' => env('B2_ENDPOINT'), // https://s3.us-west-004.backblazeb2.com ],
Local / any other disk
DB_BACKUP_DISK=local
Usage
# back up config('database-backup.connection') php artisan db:backup # back up a specific connection php artisan db:backup --connection=pgsql # list what's on the disk php artisan db:backups
Schedule it in routes/console.php:
use Illuminate\Support\Facades\Schedule; Schedule::command('db:backup')->dailyAt('02:00');
Restore
# restore the most recent backup for the connection php artisan db:restore --latest # restore a specific file from the disk php artisan db:restore backups/pgsql-app-2026-09-01_020000.sql.gz # skip the "this overwrites the database" prompt (for scripts) php artisan db:restore --latest --force
The file is pulled from the backup disk, gunzipped if needed, then piped into
psql / mysql (SQLite is copied back over the database file). PostgreSQL
restores run in a single transaction with ON_ERROR_STOP, and the default
pg_dump options include --clean --if-exists so a restore drops existing
objects first (mysqldump already emits DROP TABLE IF EXISTS).
Configuration (config/database-backup.php / env)
| Option | Env | Default | Purpose |
|---|---|---|---|
connection |
DB_BACKUP_CONNECTION |
DB_CONNECTION |
Connection to dump |
disk |
DB_BACKUP_DISK |
local |
Destination filesystem disk |
path |
DB_BACKUP_PATH |
slug of APP_NAME |
Root folder on the disk (so several sites can share one bucket) |
compress |
DB_BACKUP_COMPRESS |
true |
gzip the dump before upload |
retention.keep_last |
DB_BACKUP_KEEP_LAST |
7 |
Keep at most N backups for the connection (0 = unlimited) |
retention.keep_days |
DB_BACKUP_KEEP_DAYS |
30 |
Delete backups older than N days (0 = off) |
timeout |
DB_BACKUP_TIMEOUT |
900 |
Max seconds for the dump |
temp_directory |
DB_BACKUP_TEMP_DIR |
system temp dir | Local scratch dir (dump/restore) |
binaries.* |
DB_BACKUP_BIN_MYSQLDUMP / DB_BACKUP_BIN_MYSQL / DB_BACKUP_BIN_PGDUMP / DB_BACKUP_BIN_PSQL |
on $PATH |
Paths to mysqldump / mysql / pg_dump / psql |
extra_options.* |
— | see config | Raw args appended per driver (mysql, mariadb, pgsql) |
Backups are named {connection}-{database}-{Y-m-d_His}.sql (.sqlite for SQLite,
.gz appended when compression is on).
Events
AmdadulHaq\DatabaseBackup\Events\BackupCompleted—connection,disk,remotePath,bytesAmdadulHaq\DatabaseBackup\Events\BackupFailed—connection,exception
use AmdadulHaq\DatabaseBackup\Events\BackupFailed; use Illuminate\Support\Facades\Event; Event::listen(BackupFailed::class, function (BackupFailed $event): void { logger()->error("DB backup failed for {$event->connection}", [ 'error' => $event->exception->getMessage(), ]); });
Drivers
Each database type is a small class implementing the Dumper contract
(src/Dumpers/) and the Restorer contract (src/Restorers/):
| Driver | Backup | Restore |
|---|---|---|
pgsql |
pg_dump |
psql |
mysql / mariadb |
mysqldump |
mysql |
sqlite |
file copy | file copy |
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Credits
License
The MIT License (MIT). Please see License File for more information.