phattarachai / db-snapshot-sync-laravel
Rebuild a Laravel dev database from production in one command — driver-aware dump sanitizer, sync orchestrator, and a token-protected internal snapshot API. Extends spatie/laravel-db-snapshots.
Package info
github.com/phattarachai/db-snapshot-sync-laravel
pkg:composer/phattarachai/db-snapshot-sync-laravel
Requires
- php: ^8.4
- illuminate/console: ^12.0 || ^13.0
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/filesystem: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/routing: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- spatie/laravel-db-snapshots: ^2.10
Requires (Dev)
- driftingly/rector-laravel: ^2.5
- larastan/larastan: ^3.10
- laravel/pint: ^1.29
- orchestra/testbench: ^10.8|^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- rector/rector: ^2.5
README
Rebuild a Laravel developer's local database from production in one command. Extends
spatie/laravel-db-snapshots with the three
pieces every project otherwise reimplements: a driver-aware dump sanitizer, a sync
orchestrator, and the token-protected internal API the source server exposes.
snapshot:sync --fresh
→ trigger a fresh dump on production (internal API)
→ download the .sql.gz
→ snapshot:sed (strip directives that reject on a local client)
→ snapshot:load --drop-tables --force --stream
Supports PostgreSQL and MySQL/MariaDB.
Why
spatie/laravel-db-snapshots dumps and loads, but a dump made by a production pg_dump /
mysqldump rejects on a developer's locally-installed client — psql meta-commands (\restrict,
SET transaction_timeout), owner/grant lines, or MySQL DEFINER= clauses that need SUPER.
This package strips those, and adds the download side so the whole "copy prod down to local" loop
is one command instead of copy-pasted per project.
Install
composer require phattarachai/db-snapshot-sync-laravel php artisan db-snapshot-sync:install
The installer publishes the config, writes the env keys (generating a token), and prints the
source-side snippets you paste into config/db-snapshots.php, config/filesystems.php,
config/database.php (dump exclusions) and the scheduler. It requires
spatie/laravel-db-snapshots and a snapshots filesystem disk on both ends.
Commands
snapshot:sed {name?} {--latest}— sanitize a snapshot on the disk so it re-imports cleanly. Postgres mutates in place; MySQL writes a separate.sanitized.sql.gz.snapshot:sync {--fresh} {--source=production} {--no-load} {--keep-raw}— pull the latest snapshot from a source, sanitize, and load into the local DB. Runs only in the environments listed indb-snapshot-sync.sync.allowed_environments.
Start with a dry run once the source URLs are set:
php artisan snapshot:sync --no-load # download + sanitize, no DB clobber php artisan snapshot:sync # full sync from production
The source-side API
Set DB_SNAPSHOT_SYNC_API=true (and the shared INTERNAL_API_TOKEN) on production/UAT to expose:
| Method | Route | Purpose |
|---|---|---|
| GET | /internal/snapshots |
list servable snapshots |
| POST | /internal/snapshots |
create a fresh one synchronously (--fresh) |
| GET | /internal/snapshots/latest |
stream the newest download |
EnsureInternalToken 404s the whole group in local and checks a hash_equals bearer token
otherwise; the routes carry throttle:5,1. The consumer sends the matching token from its .env.
Configuration
config/db-snapshot-sync.php covers the disk name, token, source URLs, snapshot:load flags,
the per-driver sanitizer rules (add a prefix/sed expression when a new dump quirk appears), and
the API's reject-list (never serve a schema-only baseline or a .sanitized. intermediate).
dump.exclude_table_data lists framework caches and transient queues (cache, sessions,
jobs, pulse_*, telescope_*, …) whose data is skipped when the source builds a sync
snapshot — the schema is still dumped, and a project's own rollback snapshots are untouched. This
keeps the dev copy small; real domain tables are always included. The Postgres sanitizer streams
the dump line-by-line, so a multi-GB snapshot sanitizes without loading the whole file into memory.
dump.rows_per_insert (default 1000) batches the sync snapshot into multi-row INSERTs.
laravel-db-snapshots forces --inserts because its loader restores through PDO (which can't stream
COPY), so a large table otherwise restores one round-trip per row — a million-row table can take
tens of minutes. Batching cuts that to a couple of statements' worth of work. Applies to the sync
snapshot only; the committed baseline stays single-row so it keeps diffing line-by-line.
Testing
composer test
License
MIT. See LICENSE.md.