velikonja/sync-fs
A Laravel/PHP filesystem synchronization package for keeping files and directories in sync across local and remote disks. It provides a simple way to compare and mirror storage using Flysystem, making deployment, backups, and environment file syncing easier.
SyncFS is a small, single-purpose library: you declare folder pairs in a configuration file and it copies changed files from source to destination, either through a CLI binary or by instantiating the library classes directly. It predates Laravel's filesystem abstractions being idiomatic for this job, so treat it as a thin wrapper around copy/compare logic rather than a deployment tool.
Minimal steps:
composer require --dev velikonja/sync-fs
vendor/bin/syncfs (older packages sometimes ship a bin/ script you invoke directly). Run vendor/bin/syncfs --help and vendor/bin/syncfs list if the CLI is Symfony Console–based — that tells you the real command name and options faster than the README will.example/, config/, or the README snippet). The config is a plain structured file declaring named jobs, each with a source and a destination path. Copy it into your project as syncfs.json (or .yml, depending on the format the parser expects) rather than hand-writing keys.{
"jobs": {
"assets": {
"source": "/var/www/app/public/build",
"target": "/mnt/staging/public/build"
}
}
}
vendor/bin/syncfs assets (or vendor/bin/syncfs sync assets) and diff the two trees to confirm behavior before trusting it.First use case: mirroring a build output directory or an uploads folder between two local paths or mounted volumes — not S3, not FTP, not a real deployment pipeline.
Composer script hooks. The lowest-friction integration is a post-build or post-install hook, so asset mirroring happens without anyone remembering to run it:
"scripts": {
"post-install-cmd": ["@sync-assets"],
"sync-assets": "syncfs assets"
}
Artisan command wrapper. If you want logging, env-aware config, and a proper exit code, wrap the library rather than shelling out:
class SyncAssets extends Command
{
protected $signature = 'sync:assets {job=assets}';
public function handle()
{
$config = config('syncfs'); // your own Laravel config
$syncer = new \Velikonja\SyncFS\...; // check the concrete class name in the source
$syncer->run($config['jobs'][$this->argument('job')]);
$this->info('Sync complete.');
return self::SUCCESS;
}
}
Wrapping also lets you inject Storage-style path helpers and swap in a fake during tests.
Environment-specific job files. Keep one config per environment (syncfs.local.json, syncfs.staging.json) and select via .env, since paths for a mounted volume on a laptop never match the server. Pass the file path as a CLI option where supported, or resolve it in your wrapper.
Scheduler-driven mirroring. For periodic one-way copies (e.g. syncing a media directory to a backup mount), schedule the wrapper and guard against overlap:
$schedule->command('sync:media')->hourly()->withoutOverlapping();
Deploy pipeline step. Run it as a discrete task after the build artifact is produced and before the app is switched live. Because it is one-way and stateless, it composes cleanly with Envoy or Deployer tasks — but keep it off the critical path until you've verified behavior.
API over CLI inside tests. Instantiate the syncer directly in a feature test to assert that a fixture directory lands where expected; this is faster and more debuggable than asserting on subprocess output.
composer why and a quick read of the source (it's small) is worth more than trusting the abstraction.artisan from a different directory silently syncs the wrong tree.withoutOverlapping() in the scheduler or flock in CI.www-data (or vice versa). Set the umask or chown in the deploy step.syncfs job || exit 1 — a silent non-zero in a deploy hook is worse than a hard failure.rsync or rclone first.Given the maintenance status, the honest recommendation is: use it for a throwaway internal asset mirror, keep it isolated behind a thin wrapper so you can swap the implementation, and default to rsync for anything durable.
How can I help you explore Laravel packages today?