The NumenCode SyncOps plugin for Winter CMS offers a powerful and streamlined solution for managing backups, deployments, and environment synchronization. Designed for developers, it simplifies syncing databases, media files, and code between environments, enabling safer and more efficient DevOps workflows within Winter CMS.
The target audience for this plugin includes Winter CMS developers, DevOps engineers, and technical teams who manage multiple environments (local, staging, production) and require reliable tools for synchronization and deployment. SyncOps is ideal for teams working on complex, multi-instance projects where keeping databases, media assets, and codebases aligned is critical. It streamlines routine operations and reduces the risk of manual errors, supporting a more automated and professional development workflow.
This package requires Winter CMS application.
Install the package with Composer:
composer require numencode/wn-syncops-plugin
Run the command:
php artisan vendor:publish --tag=syncops-config
The above command will create a new configuration file, located at /config/syncops.php
, that contains all the options
you need in order to configure your remote connections. The connections array contains a list of your servers keyed
by name. Simply populate the credentials in the connections array via your environment variables in the .env
file.
Before using SyncOps, you must define your remote servers and project settings in config/syncops.php
.
Each connection entry (e.g. production
, staging
) describes how SyncOps should connect and operate on that server.
'timestamp' => 'Y-m-d_H_i_s',
Defines the default timestamp format used for naming files (backups, database dumps, archives).
Defaults to Y-m-d_H_i_s
.
All remote servers are defined under the connections
array.
Each server is keyed by a name (e.g. production
, staging
) and contains:
host
→ Remote server host (IP or domain)port
→ SSH port (default:22
)username
→ SSH usernamepassword
→ (Optional) SSH password (not needed with key auth)key_path
→ (Optional) Path to private key file
🔒 For security, these values are typically provided via environment variables rather than hardcoded.
path
→ Absolute path to the project root on the remote serverbranch_prod
→ Name of the production branch (default:prod
)branch_main
→ Name of the main development branch (default:main
)
If your server uses different users for deployment vs. web server runtime, you can define ownership rules:
root_user
→ Superuser and group with full access, e.g.root:root
web_user
→ Web server user/group, e.g.www-data:www-data
web_folders
→ Array of folders owned by the web user (defaults:storage
,themes
)
Required only when using syncops:db-pull
or related database commands.
database
→ Database name on the remote serverusername
→ Database usernamepassword
→ Database passwordtables
→ (Optional) Restrict synchronization to specific tables
Typical .env
configuration for a production server might look like this:
SYNCOPS_PRODUCTION_HOST=123.456.789.10
SYNCOPS_PRODUCTION_PORT=22
SYNCOPS_PRODUCTION_USERNAME=deploy
SYNCOPS_PRODUCTION_KEY=C:\Users\me\.ssh\id_rsa
SYNCOPS_PRODUCTION_PATH=/var/www/example.com
SYNCOPS_PRODUCTION_BRANCH_PROD=prod
SYNCOPS_PRODUCTION_BRANCH_MAIN=main
# Optional permission settings
# REMOTE_PRODUCTION_ROOT_USER=root:root
# REMOTE_PRODUCTION_WEB_USER=www-data:www-data
# Optional database settings
SYNCOPS_PRODUCTION_DB_DATABASE=example_db
SYNCOPS_PRODUCTION_DB_USERNAME=example_user
SYNCOPS_PRODUCTION_DB_PASSWORD="secret"
Command | Description |
---|---|
syncops:db-pull | Create a database dump on a remote server, download it, and import it locally. |
syncops:db-push | Create a database dump (compressed by default) and optionally upload it to cloud storage. |
syncops:media-pull | Download media files from the remote server via SFTP into local storage. |
syncops:media-push | Back up all media files to the specified cloud storage. |
syncops:project-backup | Create a compressed archive of project files and optionally upload it to cloud storage. |
syncops:project-deploy | Deploy the project to a remote server via Git, with optional cache clearing, Composer install, and migrations. |
syncops:project-pull | Commit untracked changes on the remote server, push them to the origin, and optionally merge them into the local branch. |
syncops:project-push | Add and commit project changes locally and push them to the remote repository. |
Create a database dump on a remote server, download it to your local project, and (by default) import it into your local database.
This command is especially useful for synchronizing development environments with production or staging data.
This command performs the following actions:
- Connects to the specified remote server.
- Creates a database dump on the remote server.
- By default, the dump is compressed as a
.sql.gz
file. - With the
--no-gzip
option, the dump is saved as a plain.sql
file. - Optionally, you can define specific tables to pull by configuring them in
config/syncops.php
underdatabase.tables
. Only the tables listed there will be included in the dump.
- By default, the dump is compressed as a
- Downloads the dump via SFTP to your local project folder.
- Optionally unzips the dump locally (if compression was used).
- Optionally imports the dump into your configured local database.
- Cleans up temporary dump files both remotely and locally.
Before using this command, ensure your remote servers are properly configured in your
Laravel application's config/syncops.php
file. The command will use the SSH connection
details from this configuration to connect to the remote host.
Example config/syncops.php
snippet for database tables:
'database' => [
'username' => 'dbuser',
'password' => 'dbpass',
'database' => 'my_database',
'tables' => [
'users',
'posts',
'comments',
],
],
If the tables
array is provided, only these tables will be included in the dump; otherwise, the entire
database will be dumped.
php artisan syncops:db-pull {server} [options]
Argument | Description |
---|---|
server |
The name of the remote server, as defined in your syncops.php configuration file.Example: php artisan syncops:db-pull production |
Option | Description |
---|---|
--timestamp= |
Date format used for naming the dump file. The default is defined in config/syncops.php .Example: php artisan syncops:db-pull production --timestamp=d-m-Y |
-g , --no-gzip |
Skips the gzip compression process and transfers the database dump as a plain .sql file.Example: php artisan syncops:db-pull production --no-gzip |
-i , --no-import |
Prevents the database dump from being automatically imported into the local database after it has been downloaded. Example: php artisan syncops:db-pull production --no-import |
This command currently only supports MySQL and MariaDB databases. Other database types supported by Laravel (PostgreSQL, SQLite, SQL Server, Redis, etc.) are not compatible.
Create a database dump (compressed by default) of your project’s default MySQL/MariaDB database, and, if specified, upload it to a cloud storage provider.
This command is especially useful for scheduled backups, ensuring your production database is safely stored locally and in the cloud.
This command performs the following actions:
- Creates a database dump of the configured default database.
- By default, the dump is compressed as a
.sql.gz
file. - With the
--no-gzip
option, the dump is saved as a plain.sql
file.
- By default, the dump is compressed as a
- Names the file using a timestamp (format configurable via option).
- Optionally uploads the file to a cloud storage provider.
- Optionally deletes the local file after upload, unless instructed to keep it.
- Optionally moves the file to a local folder if
--folder
is specified.
This command relies on Laravel’s Filesystem configuration if uploading to cloud storage.
Make sure the chosen cloud storage disk is defined in config/filesystems.php
.
php artisan syncops:db-push {cloud?} [options]
Argument | Description |
---|---|
cloud |
The optional name of the cloud storage disk where the database dump file will be uploaded. Must be configured in config/filesystems.php .Example: php artisan syncops:db-push dropbox |
Option | Description |
---|---|
--folder= |
The name of the folder where the dump file will be stored both locally and on the cloud. Example: php artisan syncops:db-push dropbox --folder=database |
--timestamp= |
Date format used for naming the dump file. The default is defined in config/syncops.php .Example: php artisan syncops:db-push dropbox --timestamp=d-m-Y |
-g , --no-gzip |
Skips the gzip compression process, saving the dump file as a plain .sql file instead of a compressed archive.Example: php artisan syncops:db-push dropbox --no-gzip |
-d , --no-delete |
Prevents the local dump file from being deleted after it has been successfully uploaded to the cloud storage. Example: php artisan syncops:db-push dropbox --no-delete |
To automate your database backups, add the command to your Winter CMS Scheduler
(typically in app/Plugin.php
or a dedicated service provider's boot()
method).
$schedule->command('syncops:db-push dropbox --folder=database')->dailyAt('02:00');
This example schedules a daily backup of your database to the dropbox
disk every day at 2:00 AM.
This command currently only supports MySQL and MariaDB databases. Other database types supported by Laravel (PostgreSQL, SQLite, SQL Server, Redis, etc.) are not compatible.
Download media files from a remote server via SFTP directly into your local storage/app
directory.
This command is especially useful for synchronizing media files in development environments with production data without including them in your Git repository.
This command performs the following actions:
- Connects to the specified remote server via SFTP.
- Recursively fetches all files under the remote
storage/app
directory. - Skips certain directories and files according to the rules below.
- Downloads each file into your local
storage/app
folder, creating directories as needed. - Optionally skips overwriting local files if
--no-overwrite
is provided. - Ensures file sizes match to avoid unnecessary downloads when the local file is identical.
Before using this command, ensure your remote servers are properly configured in your
Laravel application's config/syncops.php
file. The command will use the SSH connection
details from this configuration to connect to the remote host.
php artisan syncops:media-pull {server} [options]
Argument | Description |
---|---|
server |
The name of the remote server, as defined in your syncops.php configuration file.Example: php artisan syncops:media-pull production |
Option | Description |
---|---|
--no-overwrite |
Prevents the command from overwriting local files that already exist, which is useful for only downloading new or updated files. Example: php artisan syncops:media-pull production --no-overwrite |
- The remote path scanned is always
storage/app
. - Files inside any
/thumb/
or/resized/
directories are skipped. - Files starting with a dot (hidden files) are skipped, except
.gitignore
files which are always downloaded. - Only files that either do not exist locally or have a different file size are downloaded (unless
--no-overwrite
is used). - All directory structures from the remote server are preserved locally.
- Files are downloaded directly via SFTP, no intermediate cloud storage is used.
Efficiently upload all media files from your Winter CMS installation (specifically from the storage/app
directory)
to a specified cloud storage disk.
This command is ideal for creating routine media backups. When integrated with the Winter CMS Scheduler, it enables automated daily backups to your chosen cloud storage, ensuring your media assets are always safe and accessible.
By default, the command excludes .gitignore
files and any files located within /thumb/
directories
(which typically contain generated image thumbnails) to optimize upload times and storage space.
This command relies on Laravel’s Filesystem configuration.
Make sure the chosen cloud storage disk is defined in config/filesystems.php
.
To run the command, you need to specify the cloud storage disk name (as defined in your config/filesystems.php
).
php artisan syncops:media-push {cloud} [options]
You can also specify an optional target folder within your cloud storage which will serve as the base directory for
your uploaded media files. If no folder is specified, the default target folder on the cloud storage will be storage/
.
Argument | Description |
---|---|
cloud |
The required name of the cloud storage disk. Must be configured in config/filesystems.php .Example: php artisan syncops:media-push dropbox |
Option | Description |
---|---|
--folder= |
The optional target folder where the media files will be stored. Applies both locally and on cloud storage. Example: php artisan syncops:media-push dropbox --folder=media/my-project |
-l , --log |
Displays details for each file as it is processed, including files that are uploaded and those that are skipped. Example: php artisan syncops:media-push dropbox --log |
-d , --dry-run |
Simulates the upload process without actually transferring any files, which is useful for testing and verifying your setup. Example: php artisan syncops:media-push dropbox --dry-run |
To automate your media backups, add the command to your Winter CMS Scheduler
(typically in app/Plugin.php
or a dedicated service provider's boot()
method).
$schedule->command('syncops:media-push dropbox')->dailyAt('03:00');
This example schedules a daily backup of your media files to the dropbox
disk every day at 3:00 AM.
Create a compressed archive of all project files and optionally upload it to the configured cloud storage.
This command relies on Laravel’s Filesystem configuration.
Make sure the chosen cloud storage disk is defined in config/filesystems.php
.
php artisan syncops:project-backup {cloud?} [options]
Argument | Description |
---|---|
cloud |
The optional name of the cloud storage disk where the archive will be uploaded. Must be configured in config/filesystems.php .Example: php artisan syncops:project-backup dropbox |
Option | Description |
---|---|
--folder= |
The folder where the archive will be stored. Applies both locally and on cloud storage. Example: php artisan syncops:project-backup --folder=backups/my-project |
--timestamp= |
Date format used for naming the archive file. The default is defined in config/syncops.php .Example: php artisan syncops:project-backup --timestamp=Y-m-d |
--exclude= |
Comma-separated list of folders to exclude from the archive. Folders storage/framework/cache , /vendor and backup folder (as defined with --folder ) are always excluded by default.Example: php artisan syncops:project-backup --exclude=node_modules,storage,tests |
-d , --no-delete |
If set, the local archive file will not be deleted after upload. Instead, it will be moved into the --folder (if specified).Example: php artisan syncops:project-backup --no-delete |
To automate your project backups, add the command to your Winter CMS Scheduler
(typically in app/Plugin.php
or a dedicated service provider's boot()
method).
$schedule->command('syncops:project-backup dropbox --folder=_backup')->weekly()->mondays()->at('2:00')
This example schedules a daily backup of your project to the dropbox
disk every monday at 2:00 AM.
Deploy the project to a remote server via Git, with optional cache clearing, Composer install, and migrations.
This command ensures a clean remote working tree before continuing. If uncommitted changes are detected on the server,
the deployment will be aborted with a notice to run syncops:project-pull
.
php artisan syncops:project-deploy {server} [options]
Before using this command, ensure your remote servers are properly configured in your
Laravel application's config/syncops.php
file. The command will use the SSH connection
details from this configuration to connect to the remote host.
Argument | Description |
---|---|
server |
The name of the remote server (as defined in config/syncops.php ).Example: php artisan syncops:project-pull production --pull |
Option | Description |
---|---|
-f , --fast |
Perform a fast deploy without clearing or rebuilding the cache. Example: php artisan syncops:project-pull production --fast |
-c , --composer |
Force composer install --no-dev on the remote server.Example: php artisan syncops:project-pull production --composer |
-m , --migrate |
Run database migrations (php artisan winter:up ) after deployment.Example: php artisan syncops:project-pull production --migrate |
-x , --sudo |
Execute remote commands as super user (sudo ).Example: php artisan syncops:project-pull production --sudo |
- Checks if the remote working directory is clean:
- If not, the process is aborted, and you will be prompted to run
syncops:project-pull
.
- If not, the process is aborted, and you will be prompted to run
- Two deployment modes are supported:
- Full deploy (default): Puts the app in maintenance mode, clears caches, updates via Git, rebuilds caches, and brings the app back up.
- Fast deploy (
--fast
): Skips cache clearing/rebuilding, updates via Git directly.
- Deployment strategies:
- Pull mode (
branch_main
set tofalse
in config): Runsgit pull
. - Merge mode (
branch_main
set to a branch name): Runsgit fetch
andgit merge origin/{branch}
. - If
branch
orbranch_prod
is configured, the remote branch is pushed back to origin after a successful merge.
- Pull mode (
- Post-deploy steps:
- Runs Composer install (with
--no-dev
option) if--composer
is specified or ifcomposer.lock
changed. - Runs migrations if
--migrate
is specified. - Adjusts file/folder ownership according to
permissions
config.
- Runs Composer install (with
The syncops:project-pull
command automates the process of synchronizing a remote production environment's code
changes with your local development project. It's particularly useful for Winter CMS projects where content changes
made in the backend (pages, layouts, etc.) are saved as static .htm
files.
This command performs the following actions:
- Checks for and commits any untracked changes on the remote server.
- Optionally executes a
git pull
on the remote to ensure it's up-to-date before pushing. - Pushes the committed changes from the remote server to your Git repository.
- Optionally fetches the changes and merges them into your current local branch.
This streamlined workflow ensures you can quickly and safely retrieve and integrate content updates made directly on your production site, without manual intervention.
To run the command, you need to specify the name of the remote server, as configured in your config/remote.php
file.
php artisan syncops:project-pull {server} [options]
Before using this command, ensure your remote servers are properly configured in your
Laravel application's config/syncops.php
file. The command will use the SSH connection
details from this configuration to connect to the remote host.
Argument | Description |
---|---|
server |
The name of the remote server (as defined in config/syncops.php ).Example: php artisan syncops:project-pull production --pull |
Option | Description |
---|---|
-p , --pull |
Executes a git pull on the remote server before pushing changes. Use this to ensure the remote server's branch is fully up-to-date with the repository's origin before pushing its new changes.Example: php artisan syncops:project-pull production --pull |
-m , --no-merge , |
Prevents the command from automatically merging changes into your local branch after pushing them to the repository. This is useful if you want to inspect the changes on your local machine before merging them manually. Example: php artisan syncops:project-pull production --no-merge |
--message= |
Specify a custom commit message for the changes on the remote server. If this option is not used, the default commit message is "Server changes". Example: php artisan syncops:project-pull production --message="Updated content and layout" |
The syncops:project-push
command automates the process of committing and pushing local project changes to your
Git repository. It’s especially useful when working on Winter CMS projects where content or code adjustments
have been made directly on a development or staging server, and you want to persist those changes in Git.
This command performs the following actions:
- Checks for uncommitted changes in your local working directory.
- If changes are detected, stages all modified and new files (
git add --all
). - Commits the changes with either a default or custom commit message.
- Pushes the commit to your remote Git repository.
This ensures that local adjustments are always tracked and versioned, keeping your project repository in sync with your environment.
This command runs entirely locally and does not require a remote server configuration. It uses your current Git settings (branch, remote, and authentication) to push changes.
To run the command:
php artisan syncops:project-push [options]
By default, this will commit with the message "Server changes" and push them to your configured remote.
Option | Description |
---|---|
--message= |
Specify a custom commit message for the changes. If this option is not used, the default commit message is "Server changes". Example: php artisan syncops:project-push --message="Updated content and layout" |
The recommended entries for the Scheduler are as follows:
- create a complete project backup every monday at 1 am and push it to a cloud
- create a backup of all media files every day at 2 am and push it to a cloud
- create a backup of the database every day at 3 am and push it to a cloud
- commit changes from the production environment every day at 4 am
$schedule->command('syncops:project-backup dropbox --folder=files')->weeklyOn(1, '01:00');
$schedule->command('syncops:db-push dropbox --folder=database')->daily()->at('02:00');
$schedule->command('syncops:media-push dropbox')->daily()->at('03:00');
$schedule->command('syncops:project-push')->daily()->at('04:00');
Dropbox is very easy to configure and upon the registration on Dropbox website it offers free 2GB of cloud storage space. In order to setup the Dropbox, complete the registration, add the NumenCode Dropbox Adapter Plugin and follow the Installation steps here.
All notable changes are documented in the CHANGELOG.
Please refer to the CONTRIBUTING guide for details on contributing to this project.
If you identify any security issues, email info@numencode.com rather than using the issue tracker.
The NumenCode.SyncOps plugin is created and maintained by Blaz Orazem.
For inquiries, contact: info@numencode.com
This project is open-sourced software licensed under the MIT license.