A reusable, scalable Docker-based local development environment for WP Engine WordPress sites. Designed to work with 1 site or 1,000+ sites.
- Reusable: Single common repository used across all WP Engine projects
- Scalable: Dynamic port allocation supports multiple concurrent environments
- Automated: One-command setup syncs database, plugins, and uploads from WP Engine
- Modular: Separate scripts for database, plugin, and upload synchronization
- API-Ready: Architecture supports future API-driven configuration for thousands of clients
- Cross-Platform: Works on Linux, macOS, Windows (WSL2), and Raspberry Pi
your-wpengine-project/
├── .wpengine-local/ # Git submodule (this repo)
│ ├── setup.sh # Main setup script
│ ├── unsetup.sh # Cleanup script
│ ├── scripts/
│ │ ├── sync-database.sh # Database sync
│ │ ├── sync-plugins.sh # Plugins sync
│ │ ├── sync-uploads.sh # Uploads sync
│ │ └── lib/
│ │ └── config-parser.sh # Configuration utilities
│ └── templates/
│ ├── docker-compose.yml.template
│ ├── local-config.yml.example
│ └── ...
├── local-config.yml # Your project config (~100 lines)
├── docker-compose.yml # Generated from template
├── docker/ # Generated Docker configs
├── plugins/ # Synced from WP Engine
├── themes/ # Your custom themes (Git tracked)
├── mu-plugins/ # Synced from WP Engine
└── uploads/ # Synced from WP Engine (optional)
cd your-wpengine-project
git submodule add https://github.com/Originate-Group/common-wpengine-local-dev.git .wpengine-localcp .wpengine-local/templates/local-config.yml.example local-config.ymlEdit local-config.yml with your WP Engine details:
client:
project_name: myclient
wpengine:
environment: myclientdev
ssh_user: myclientdev
ssh_host: myclientdev.ssh.wpengine.net
ssh_key: ~/.ssh/wpengine_deploy
local:
ports:
wordpress: 8000
phpmyadmin: 8001
mysql: 3306./.wpengine-local/setup.shThat's it! Your local environment is now running.
- WordPress: http://localhost:8000
- phpMyAdmin: http://localhost:8001
- WP Admin: http://localhost:8000/wp-admin
- Docker (20.10+) and Docker Compose (2.0+)
- SSH access to WP Engine
- rsync (for syncing plugins/uploads)
- Git (for managing the submodule)
macOS:
brew install --cask docker
brew install rsyncLinux (Ubuntu/Debian):
sudo apt update
sudo apt install docker.io docker-compose rsync
sudo usermod -aG docker $USER # Log out and back inWindows:
- Install Docker Desktop for Windows
- Enable WSL2 integration
- Use WSL2 terminal for all commands
The local-config.yml file controls your local environment. See templates/local-config.yml.example for all available options.
client:
id: my-client-unique-id
name: "My Client Name"
project_name: myclient # Used for Docker container nameswpengine:
environment: myclientdev
ssh_user: myclientdev
ssh_host: myclientdev.ssh.wpengine.net
ssh_key: ~/.ssh/wpengine_deploylocal:
ports:
wordpress: 8000 # Change if port is in use
phpmyadmin: 8001
mysql: 3306Important: Each project running simultaneously must use unique ports!
sync:
database: true # Sync database from WP Engine
plugins: true # Sync plugins from WP Engine
uploads: false # Sync media files (can be LARGE!)# Start containers
docker compose up -d
# Make changes to themes/plugins
# (changes are live-mounted, no sync needed)
# Stop containers
docker compose down# Sync database (updates local DB from WP Engine)
./.wpengine-local/scripts/sync-database.sh
# Sync plugins (downloads latest plugins)
./.wpengine-local/scripts/sync-plugins.sh
# Sync uploads (downloads media files - can be large!)
./.wpengine-local/scripts/sync-uploads.sh# Run WP-CLI commands
docker compose run --rm wpcli wp plugin list
docker compose run --rm wpcli wp user list
docker compose run --rm wpcli wp cache flush# View logs
docker compose logs -f
# View specific service logs
docker compose logs -f wordpress
# Restart a service
docker compose restart wordpress
# Execute commands in container
docker compose exec wordpress bash# Complete cleanup (removes containers, volumes, generated files)
./.wpengine-local/unsetup.shRunning multiple WP Engine projects simultaneously? No problem!
Each project needs unique ports. Use this pattern:
| Project | WordPress | phpMyAdmin | MySQL |
|---|---|---|---|
| Client 1 | 8000 | 8001 | 3306 |
| Client 2 | 8010 | 8011 | 3316 |
| Client 3 | 8020 | 8021 | 3326 |
| Client 4 | 8030 | 8031 | 3336 |
Configure in each project's local-config.yml:
local:
ports:
wordpress: 8010 # Different for each project
phpmyadmin: 8011
mysql: 3316Ensure each project has a unique project_name:
client:
project_name: client1 # Must be unique!This creates uniquely named containers:
client1-wordpressclient1-mysqlclient2-wordpressclient2-mysql
This architecture is designed to scale from 1 to 1,000+ clients:
- Configuration in
local-config.ymlfile - Manual setup per project
- Works great for 1-50 projects
The architecture supports future API-driven configuration:
mode: api # Switch to API mode
platform:
api_url: https://devops.originate.com/api
api_key: your-api-key
client_id: auto-fetched-from-apiIn API mode:
- Central database stores all client configurations
- Dynamic port allocation prevents conflicts
- One-click provisioning via web UI
- Automated client onboarding
common-wpengine-local-dev/
├── README.md # This file
├── LICENSE # MIT License
├── setup.sh # Main setup script
├── unsetup.sh # Cleanup script
├── scripts/
│ ├── sync-database.sh # Database sync
│ ├── sync-plugins.sh # Plugins sync
│ ├── sync-uploads.sh # Uploads sync
│ └── lib/
│ └── config-parser.sh # Config utilities
├── templates/
│ ├── local-config.yml.example # Config template
│ ├── docker-compose.yml.template # Docker Compose template
│ ├── php.ini.template # PHP config template
│ ├── .dockerignore.template # Docker ignore template
│ └── .gitignore.template # Git ignore template
└── .github/
└── workflows/
└── ... # CI/CD workflows
Error: Bind for 0.0.0.0:8000 failed: port is already allocated
Solution: Change ports in local-config.yml:
local:
ports:
wordpress: 8010 # Use a different portError: Container exits immediately
Solution:
# Remove volumes and start fresh
docker compose down -v
./.wpengine-local/setup.shError: Permission denied (publickey)
Solution:
- Verify SSH key exists:
ls -la ~/.ssh/wpengine_deploy - Test connection:
ssh -i ~/.ssh/wpengine_deploy user@host.ssh.wpengine.net - Add key to WP Engine: WP Engine SSH Keys Guide
Error: rsync: command not found
Solution:
# macOS
brew install rsync
# Linux
sudo apt install rsync
# Windows (WSL2)
sudo apt install rsyncSolution:
# Wait for MySQL to be ready
docker compose logs mysql
# If needed, restart WordPress container
docker compose restart wordpressError: Files owned by root, can't edit
Solution:
# Fix permissions
sudo chown -R $USER:$USER themes/ plugins/
# Or run Docker without sudo
sudo usermod -aG docker $USER # Log out and back in- Never edit plugins downloaded from WP Engine (they'll be overwritten on next sync)
- Custom plugins: Add to
.gitignoreexceptions:# .gitignore plugins/* !plugins/index.php !plugins/my-custom-plugin/ # Track this - Themes: Keep in Git, develop locally, deploy via Git
- Database changes: Make in WP admin, export with WP-CLI if needed
- Uploads: Don't commit to Git (sync from WP Engine as needed)
- uploads/: Only sync if you need media files (can be GBs)
- Docker: Use native Docker on Linux for best performance
- WSL2: Store project files in WSL filesystem, not Windows drives
- macOS: Consider increasing Docker Desktop resources
- SSH Keys: Never commit private keys to Git
- Database Passwords: Use strong passwords in
local-config.yml - local-config.yml: Add to
.gitignoreif it contains secrets - WP-CLI: Always use
--allow-rootwith caution
Improvements welcome! This is a shared resource for the Originate Group organization.
git clone https://github.com/Originate-Group/common-wpengine-local-dev.git
cd common-wpengine-local-dev
# Test changes
./setup.shWhen you update this common repo, all projects using it as a submodule can update:
cd your-project
git submodule update --remote .wpengine-local
git add .wpengine-local
git commit -m "Update common-wpengine-local-dev"MIT License - see LICENSE file for details.
- Issues: GitHub Issues
- Docs: WP Engine Documentation
- Internal: Contact DevOps team
Built with ❤️ by Originate Group