phattarachai/db-snapshot-sync-laravel
A Laravel package for capturing database snapshots and syncing them across environments. It simplifies backing up, transferring, and restoring database state during development, testing, and deployment, with Artisan commands and configurable connections.
phattarachai/db-snapshot-sync-laravel gives you a single artisan command, snapshot:sync, for moving database snapshots between environments — the classic use case being "refresh my local database from a known-good snapshot without hand-rolling mysqldump + scp + import every time."
Install and publish the config first, then read the published sync block before running anything:
composer require phattarachai/db-snapshot-sync-laravel
php artisan vendor:publish # select the package provider
As of v1.4.0 the shipped default is a deny list, not an allow list:
'sync' => [
'denied_environments' => ['production'], // env: DB_SNAPSHOT_SYNC_DENIED_ENVIRONMENTS
'allowed_environments' => [], // env: DB_SNAPSHOT_SYNC_ALLOWED_ENVIRONMENTS
],
That means the command is runnable in local, development, staging, testing, and anything else you name — everything except production. Your first run should be a deliberate one in local after you've confirmed APP_ENV is what you think it is:
php artisan snapshot:sync
The first real use case worth wiring up is the onboarding path: a new developer clones the repo, runs migrate, then snapshot:sync to get representative data instead of seeding ten rows of fixtures.
Pick one gating strategy and stick to it. The precedence rule is the whole design of this feature: if allowed_environments is non-empty it decides alone — denied_environments is not consulted at all. So you have two mutually exclusive models:
APP_ENV shouldn't be survivable.Don't half-migrate — a config with allowed => ['local'] and denied => ['production', 'staging'] reads like both are enforced, but only the allow list is.
Drive it from environment, not committed config. In config/db-snapshot-sync.php leave the arrays reading from env, then set the variables per environment in your .env, deploy secrets, or CI variables. That keeps the committed config diff-free across environments and lets a single developer override locally without touching tracked files.
Wrap it in a composer script or Makefile target so the sequence is explicit rather than tribal knowledge:
"scripts": {
"db:refresh": [
"@php artisan migrate:fresh --no-interaction",
"@php artisan snapshot:sync"
]
}
Whatever snapshot:sync does to the target schema, ordering it against migrate:fresh is the difference between a clean local DB and a confusing half-migrated one. Verify which direction the package expects before encoding it in a script.
Treat it as a dev-environment primitive in CI. The v1.4.0 change makes this the natural pattern: a preview/review-app environment no longer has to be added to an allow list by name — it just runs, because it isn't production. Reserve the env vars for the environments you actually want to block.
Keep secrets out of the sync path. Anything you use to reach the source database belongs in .env/secret storage, never in the published config file — that file is committed and PR-diffable.
The upgrade footgun is the headline risk in v1.4.0. If you published config under 1.3 or earlier, it hard-codes 'allowed_environments' => ['local', 'development']. That non-empty array still wins, so you will not observe the new behaviour at all — snapshot:sync keeps refusing to run in staging and you'll assume the release is broken. After upgrading, diff your published config against the package's and replace the sync block wholesale rather than editing around it.
Default-open is a real behaviour change. Pre-1.4 defaulted to running in local/development; 1.4 defaults to running everywhere except production. Any environment name that isn't literally production in your denied list is now permitted. If you have prod, live, production-eu, a blue/green production slot, or a staging box pointed at production data, add them explicitly:
DB_SNAPSHOT_SYNC_DENIED_ENVIRONMENTS=production,prod,live,staging,production-eu
This is exactly the kind of change that deserves a grep across your .env, CI config, and deploy manifests rather than a config-cache-clear-and-hope.
APP_ENV is the gate, so it is also the failure mode. Anywhere the environment name is derived (queue workers, scheduled tasks, container entrypoints, phpunit.xml), the value may differ from what you assume. Confirm with php artisan env or app()->environment() in tinker on the box in question before trusting a green or red result.
Config caching will make you doubt your own memory. After changing any of the env vars, run php artisan config:clear (or config:cache in deploy pipelines). A stale bootstrap/cache/config.php silently serves the old arrays, and the symptom — "it still runs in staging, I definitely changed the variable" — looks identical to a precedence bug.
Check the env var list format before trusting it. These are array-valued settings fed from a single string; confirm the expected separator in the package's config comments and test it once with a deliberately wrong value. A malformed value that parses to a single junk environment name will fail open, not closed.
Empty is not the same as unset. allowed_environments being empty (or an env var resolving to an empty string/array) is what hands control back to the deny list. If you're programmatically building this config — a service provider, a multi-tenant bootstrap, a test — an accidental empty array silently switches gating modes. Assert on the resolved config in a test if you're doing anything dynamic.
Extension point worth knowing: because gating is pure config resolution, you can override it per-context (per-test-suite, per-tenant, per-CI-job) without forking the package — set the config at runtime in a service provider or phpunit.xml <env> block. Use this to hard-disable the command in any test suite that touches a shared database, so a stray snapshot:sync in a test can never point at something real.
How can I help you explore Laravel packages today?