sextanet / laravel-database-copy
Copy databases between environments, anonymized before they leave the source
Fund package maintenance!
Requires
- php: ^8.4
- ext-zip: *
- fakerphp/faker: ^1.24
- illuminate/contracts: ^13.0
- spatie/laravel-backup: ^10.3
- spatie/laravel-package-tools: ^1.16
- wnx/laravel-backup-restore: ^1.9
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0
- pestphp/pest: ^5.0
- pestphp/pest-plugin-arch: ^5.0
- pestphp/pest-plugin-laravel: ^5.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-05 01:41:23 UTC
README
Copy a database between environments (e.g. production → staging) through a disk such as S3. The copy is anonymized before it leaves the source server, so the real data never reaches the bucket nor the other environment.
Production ──database-copy:export──▶ dump → anonymization database (same server) → anonymize → dump → encrypt
│
disk: {environment}/{name}/{Y-m-d-H-i-s}.zip
│
Staging ◀──database-copy:import── decrypt → check it is anonymized → replace database → migrate ┘
Installation
composer require sextanet/laravel-database-copy
php artisan vendor:publish --tag="database-copy-config"
DATABASE_COPY_DISK=s3 DATABASE_COPY_PASSWORD= # the SAME in every environment DATABASE_COPY_NAME=my-app # folder of this app in the disk (default: slug of APP_NAME) # Source environment only DATABASE_COPY_ANONYMIZATION_DATABASE= # default: {database}_anonymized DATABASE_COPY_EMAIL_DOMAIN=anonymized.test DATABASE_COPY_KEEP_EMAILS=seba@sextanet.cl # rows that keep their data, comma separated
The source environment needs mysqldump/mysql (or sqlite3, pg_dump/psql) and an empty anonymization
database on the same server that the database user can write to:
CREATE DATABASE my_app_anonymized; GRANT ALL PRIVILEGES ON my_app_anonymized.* TO 'my_app'@'localhost';
It is emptied after every export, even when something fails. With SQLite there is nothing to create: the file
(database/database_anonymized.sqlite by default) is created and deleted on every export.
Anonymization
config/database-copy.php is the source of truth of what is personal data. Add every table or column with personal
data there:
'tables' => [ 'users' => [ 'name' => 'first_name', 'last_name' => 'last_name', 'rut' => 'rut', 'email' => 'email', 'phone' => 'phone', 'birthday' => 'null', 'password' => 'password', 'remember_token' => 'null', ], 'comments' => [ 'email' => 'email', 'ip' => 'ip', 'user_agent' => 'user_agent', 'address' => 'address', // any Faker formatter ], ], 'truncate' => ['sessions', 'password_reset_tokens', 'personal_access_tokens', 'jobs', 'failed_jobs'],
Tables or columns that do not exist are skipped. Anonymized tables need an id column.
Usage
# Source (production): upload an anonymized copy php artisan database-copy:export # Target (staging, local): replace this database with the latest copy from production php artisan database-copy:import php artisan database-copy:import --from=staging php artisan database-copy:import --file=production/my-app/2026-10-04-03-30-00.zip
Copies are named with the date of the source app in its timezone (config('app.timezone'), UTC by default).
Schedule the export in the source environment:
Schedule::command('database-copy:export')->dailyAt('03:30');
Safety:
database-copy:importnever runs inprotected_environments(productionby default) and asks for confirmation (unless--force).- It refuses a copy that is not anonymized (
database-copy:export --without-anonymization) unless--with-real-data. - The copy is decrypted and checked before the database is touched.
- The export refuses to anonymize when the anonymization database is the source database.
Testing
composer test
Credits
License
The MIT License (MIT). Please see License File for more information.