WordPress VIP Local Development with vip dev-env: From Setup to Production Parity
WordPress VIP is the enterprise WordPress hosting platform trusted by some of the largest publishers and brands on the internet. Organizations like TechCrunch, Salesforce, Meta Newsroom, and Bloomberg rely on it for scale, security, and performance. But building locally for VIP has historically been a frustrating experience. The platform enforces strict constraints around file writes, caching layers, cron behavior, and plugin architecture that standard local environments simply do not replicate.
That changed when Automattic released vip dev-env, a Docker-based local development tool purpose-built for WordPress VIP projects. I have spent the better part of two years running this tool across multiple client engagements, on machines ranging from beefy Linux workstations to resource-constrained MacBook Airs. This article is a thorough, opinionated guide to getting the most out of it.
What vip dev-env Is and Why It Exists
The vip dev-env command is part of the VIP CLI, Automattic’s official command-line interface for interacting with the WordPress VIP platform. Unlike generic local WordPress tools, vip dev-env creates a containerized environment that mirrors VIP’s production infrastructure with high fidelity. That means Memcached for object caching, a read-only file system outside of /tmp, restricted cron execution, and the same MU plugin architecture that VIP enforces on its servers.
Why does this matter? Because the number one source of bugs on VIP deployments is code that works perfectly on a standard WordPress install but breaks under VIP’s constraints. A plugin that writes cache files to wp-content will fail silently. A theme that relies on wp_schedule_event firing at precise intervals will behave differently. An integration that assumes direct filesystem access to uploaded media will produce errors. Testing locally against an environment that actually mirrors these constraints catches these issues before they reach production.
Before vip dev-env, the standard approach was a patchwork: run Local by Flywheel or MAMP, manually install the VIP MU plugins, hack together a Memcached setup, and hope for the best. Some teams maintained custom Docker Compose files that attempted to replicate VIP’s stack. These worked, but they required significant maintenance and drifted from the actual production environment over time. The official tool eliminates that drift.
System Requirements and Docker Configuration
Before installing anything, you need Docker Desktop running and properly configured. This is where many developers hit their first wall, so let me be specific about what “properly configured” means.
macOS Requirements
Docker Desktop for Mac should be version 4.0 or later. Open Docker Desktop, go to Settings (the gear icon), then Resources. You need to allocate:
- CPUs: Minimum 2, recommended 4
- Memory: Minimum 4 GB, recommended 6 GB or more
- Swap: At least 1 GB
- Disk image size: At least 60 GB (VIP images are not small)
If you are on an Apple Silicon Mac (M1, M2, or later), you are in luck. Docker runs significantly better on ARM-based Macs than it did on Intel. The Rosetta 2 emulation layer handles the x86 images that some VIP containers require, though most have been updated to support ARM natively. Make sure “Use Rosetta for x86/amd64 emulation on Apple Silicon” is enabled in Docker Desktop’s General settings.
On Intel Macs, especially models with 8 GB of RAM, you will need to be aggressive about closing other applications while running dev-env. Chrome alone can consume 2 to 3 GB of RAM, and that combined with Docker’s overhead leaves very little for the actual WordPress containers.
Windows Requirements
Windows users need WSL 2 (Windows Subsystem for Linux) enabled and Docker Desktop configured to use the WSL 2 backend. This is the default for new installations, but if you upgraded from an older Docker Desktop version, verify this under Settings, then General, then “Use the WSL 2 based engine.”
Within your WSL 2 distribution, you will want to configure memory limits via a .wslconfig file in your Windows user profile directory:
[wsl2]
memory=6GB
processors=4
swap=2GB
Without this file, WSL 2 will consume up to 80% of your total system memory, which causes problems when Docker is also trying to allocate resources.
Linux Requirements
Linux users have it easiest. Install Docker Engine (not Docker Desktop, though Desktop works too) following the official Docker documentation for your distribution. Make sure your user is in the docker group so you do not need sudo for every command:
sudo usermod -aG docker $USER
newgrp docker
No special memory configuration is needed on Linux since Docker runs natively without a VM layer. That said, if your machine has less than 8 GB of total RAM, you will still feel the squeeze.
Installing the VIP CLI
The VIP CLI is distributed as an npm package. You need Node.js version 18 or later installed. I recommend using nvm (Node Version Manager) to manage Node versions, since different projects may require different versions:
nvm install 18
nvm use 18
Then install the VIP CLI globally:
npm install -g @automattic/vip
Verify the installation:
vip --version
You should see output like 2.30.0 or later. The version number matters because dev-env features are actively developed, and older versions may lack important fixes.
After installation, run the initial setup:
vip dev-env create
This command kicks off an interactive wizard that walks you through environment configuration. But before running it, you should understand the options so you can make informed choices rather than accepting defaults blindly.
Creating Your First Environment
The vip dev-env create wizard asks several questions. Here is what each one means and what you should choose.
Environment Slug
This is a short identifier for the environment. It is used in all subsequent commands. Keep it lowercase, no spaces, and descriptive:
vip dev-env create --slug=my-vip-project
If you are working on multiple VIP projects (common for agencies), use the client or project name. I typically use patterns like clientname-prod and clientname-staging to mirror the VIP application structure.
WordPress Version
You can pin a specific WordPress version or use the latest. VIP typically runs the latest stable WordPress release, so “latest” is usually correct:
vip dev-env create --slug=my-vip-project --wordpress=latest
However, if you are debugging a version-specific issue, pinning is invaluable:
vip dev-env create --slug=my-vip-project --wordpress=6.1.1
Multisite Configuration
If your VIP application runs as a multisite network, you must configure this at creation time. You cannot convert a single-site dev-env to multisite after the fact:
vip dev-env create --slug=my-vip-project --multisite
For subdirectory-based multisite (the default on VIP), that flag is sufficient. Subdomain-based multisite requires additional DNS configuration, which I will cover in the networking section.
Mapping Your Application Code
This is the most important configuration step. You need to tell dev-env where your application code lives locally. VIP applications have a specific repository structure:
your-vip-app/
├── client-mu-plugins/
├── images/
├── languages/
├── plugins/
├── private/
├── themes/
│ └── your-theme/
└── vip-config/
└── vip-config.php
When running the wizard, it will ask for the path to your application code. Point it at the root of this repository:
vip dev-env create --slug=my-vip-project --app-code=/path/to/your-vip-app
If you pass the --app-code flag, dev-env mounts that directory into the container. Changes you make locally are reflected immediately in the running environment. No build step, no sync delay. This is live code mounting via Docker volumes.
Custom PHP Version
VIP supports PHP 8.0 and later. You can specify the version:
vip dev-env create --slug=my-vip-project --php=8.2
Always match this to your VIP application’s PHP version in production. Running a different PHP version locally than in production defeats the purpose of using dev-env in the first place.
Putting It All Together
Here is a full creation command with all common options specified:
vip dev-env create
--slug=acme-corp
--wordpress=latest
--php=8.2
--multisite
--app-code=/Users/marcus/projects/acme-vip-app
After creation, start the environment:
vip dev-env start --slug=acme-corp
The first start takes several minutes as Docker pulls the required images. Subsequent starts are much faster since the images are cached locally. Once running, your site will be available at http://acme-corp.vipdev.lndo.site (the exact URL is shown in the terminal output).
Managing Your Running Environment
Once started, you interact with the environment through a set of subcommands. Here are the ones you will use constantly.
Starting and Stopping
# Start the environment
vip dev-env start --slug=acme-corp
# Stop the environment (preserves data)
vip dev-env stop --slug=acme-corp
# Destroy the environment (removes all data)
vip dev-env destroy --slug=acme-corp
Stopping is non-destructive. Your database, uploads, and configuration persist. Destroying wipes everything, which is useful when you want a clean slate or when troubleshooting persistent issues.
Accessing WP-CLI
You can run WP-CLI commands inside the container:
vip dev-env exec --slug=acme-corp -- wp user list
vip dev-env exec --slug=acme-corp -- wp option get siteurl
vip dev-env exec --slug=acme-corp -- wp plugin list
The double dash (--) separates vip dev-env flags from the command you want to run inside the container. Everything after -- is passed through to the container’s shell.
For frequent WP-CLI usage, I recommend creating a shell alias:
alias vipwp='vip dev-env exec --slug=acme-corp -- wp'
Then you can simply run:
vipwp user list
vipwp cache flush
vipwp cron event list
Accessing the Shell
For deeper debugging, drop into the container’s shell:
vip dev-env shell --slug=acme-corp
This gives you a bash session inside the WordPress container. You can inspect the file system, check PHP configuration, verify Memcached connectivity, or run arbitrary commands. The shell is especially useful when debugging file permission issues or verifying that your code is mounted correctly.
Viewing Logs
Application logs are essential for debugging:
vip dev-env logs --slug=acme-corp
This tails the PHP error log and the web server access log. For VIP-specific debugging, you can also check the debug log inside the container:
vip dev-env exec --slug=acme-corp -- tail -f /tmp/wordpress/debug.log
Note the path: /tmp/wordpress/debug.log. On VIP, debug logs are written to /tmp because the rest of the filesystem is read-only. This is one of those VIP-specific behaviors that dev-env replicates faithfully.
Importing Production Data
Working with realistic data is critical for enterprise WordPress projects. A theme that looks fine with five test posts may break catastrophically with 500,000 posts and complex taxonomy relationships. The vip dev-env import commands let you pull production databases and media into your local environment.
Database Import
First, export your production database from VIP. You can do this through the VIP Dashboard or via the CLI:
vip export sql @my-vip-app.production
This generates a SQL dump. Then import it into your local environment:
vip dev-env import sql --slug=acme-corp /path/to/database-export.sql
After import, you almost certainly need to run a search-replace to update URLs:
vip dev-env exec --slug=acme-corp -- wp search-replace 'https://www.acme-corp.com' 'http://acme-corp.vipdev.lndo.site' --all-tables
For multisite installations, add the --network flag and handle each site’s URL individually:
vip dev-env exec --slug=acme-corp -- wp search-replace 'https://www.acme-corp.com' 'http://acme-corp.vipdev.lndo.site' --all-tables --network
vip dev-env exec --slug=acme-corp -- wp search-replace 'https://blog.acme-corp.com' 'http://acme-corp.vipdev.lndo.site/blog' --all-tables --network
Media Import
Large media libraries can be handled in two ways. The first is a full import:
vip dev-env import media --slug=acme-corp /path/to/uploads/
This copies files into the container’s uploads directory. For large media libraries (10 GB or more), this is slow and consumes significant disk space. The alternative is to configure a production media proxy so that missing local files are fetched from production on demand. Add this to your theme’s functions.php or a custom MU plugin:
// Only in local development
if ( defined( 'VIP_GO_APP_ENVIRONMENT' ) && 'local' === VIP_GO_APP_ENVIRONMENT ) {
add_filter( 'wp_get_attachment_url', function( $url ) {
if ( ! file_exists( str_replace( site_url(), ABSPATH, $url ) ) ) {
return str_replace(
site_url(),
'https://www.acme-corp.com',
$url
);
}
return $url;
});
}
This approach means you do not need to download a single media file. Images are served from production transparently. The trade-off is that you need internet access while developing, and you cannot test media upload workflows against local storage.
Running Multiple Environments Simultaneously
One of the strongest features of vip dev-env is the ability to run multiple environments at the same time. This is essential for agencies managing multiple VIP clients or for developers who need to compare behavior across WordPress versions.
Each environment operates in its own set of Docker containers with isolated databases, file systems, and network configurations. Create them with different slugs:
vip dev-env create --slug=client-alpha --app-code=/projects/alpha
vip dev-env create --slug=client-beta --app-code=/projects/beta
vip dev-env create --slug=client-gamma --app-code=/projects/gamma
Start them all:
vip dev-env start --slug=client-alpha
vip dev-env start --slug=client-beta
vip dev-env start --slug=client-gamma
Each gets its own URL and port. The CLI output after each start command tells you exactly where to access each site.
The practical limit is your machine’s resources. Each environment consumes roughly 1 to 1.5 GB of RAM when idle and more under load. On a machine with 16 GB of RAM, I comfortably run two environments simultaneously. Three is possible but tight. On 32 GB machines, four or five environments run without issue.
To see all your environments and their status:
vip dev-env list
This shows the slug, status (running or stopped), WordPress version, and PHP version for each environment. It is the quickest way to check what is running and consuming resources.
Configuring client-mu-plugins and vip-config
VIP’s plugin architecture differs significantly from standard WordPress. Understanding this is critical for local development.
The MU Plugin Layer
VIP applications have three layers of plugins:
- VIP MU Plugins: Managed by Automattic. These provide caching integration, security hardening, media handling, and other platform features. The
vip dev-envincludes these automatically. - Client MU Plugins: Your code in
client-mu-plugins/. These are “must-use” plugins that are always active and cannot be deactivated from the WordPress admin. Use these for functionality that must always run: custom post types, taxonomies, REST API endpoints, and integration code. - Regular Plugins: Standard WordPress plugins in
plugins/. These can be activated and deactivated normally.
In your local environment, all three layers load automatically when you map your application code via --app-code. But there is a subtlety. The load order is: VIP MU Plugins first, then client MU plugins, then regular plugins. If your client MU plugin depends on a function defined by a VIP MU Plugin, it will be available. But if a regular plugin depends on something in your client MU plugins, you need to be careful about timing.
The vip-config.php File
The vip-config/vip-config.php file is VIP’s equivalent of customizing wp-config.php. On VIP, you do not have access to wp-config.php directly. Instead, you put your configuration constants in vip-config.php.
For local development, you need environment-specific configuration. The standard pattern uses the VIP_GO_APP_ENVIRONMENT constant:
<?php
// vip-config/vip-config.php
// Environment detection
if ( ! defined( 'VIP_GO_APP_ENVIRONMENT' ) ) {
define( 'VIP_GO_APP_ENVIRONMENT', 'local' );
}
// Debug settings per environment
switch ( VIP_GO_APP_ENVIRONMENT ) {
case 'local':
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', '/tmp/wordpress/debug.log' );
define( 'WP_DEBUG_DISPLAY', true );
define( 'SCRIPT_DEBUG', true );
define( 'SAVEQUERIES', true );
break;
case 'develop':
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
break;
case 'preprod':
case 'production':
default:
define( 'WP_DEBUG', false );
break;
}
// Local development overrides
if ( 'local' === VIP_GO_APP_ENVIRONMENT ) {
// Disable external HTTP requests if needed
// define( 'WP_HTTP_BLOCK_EXTERNAL', true );
// define( 'WP_ACCESSIBLE_HOSTS', 'api.wordpress.org,downloads.wordpress.org' );
// Performance monitoring off locally
define( 'WPCOM_VIP_DISABLE_PERFORMANCE_MONITORING', true );
}
Notice the SAVEQUERIES constant. On VIP production, enabling this would be a performance disaster. But locally, it lets you use tools like Query Monitor to inspect every database query. This is invaluable for performance optimization work.
Client MU Plugin Structure
A well-organized client-mu-plugins directory looks like this:
client-mu-plugins/
├── plugin-loader.php # Main loader file
├── custom-post-types/
│ ├── class-cpt-event.php
│ └── class-cpt-resource.php
├── rest-api/
│ ├── class-api-events.php
│ └── class-api-search.php
├── integrations/
│ ├── class-salesforce.php
│ └── class-analytics.php
└── utilities/
├── class-cache-helpers.php
└── class-image-helpers.php
The plugin-loader.php file is the entry point. VIP loads any PHP file in the root of client-mu-plugins/ automatically, but using a single loader keeps things organized:
<?php
// client-mu-plugins/plugin-loader.php
// Custom Post Types
require_once __DIR__ . '/custom-post-types/class-cpt-event.php';
require_once __DIR__ . '/custom-post-types/class-cpt-resource.php';
// REST API Extensions
require_once __DIR__ . '/rest-api/class-api-events.php';
require_once __DIR__ . '/rest-api/class-api-search.php';
// Integrations (with environment guards)
if ( 'local' !== VIP_GO_APP_ENVIRONMENT ) {
require_once __DIR__ . '/integrations/class-salesforce.php';
}
require_once __DIR__ . '/integrations/class-analytics.php';
// Utilities
require_once __DIR__ . '/utilities/class-cache-helpers.php';
require_once __DIR__ . '/utilities/class-image-helpers.php';
Notice the environment guard around the Salesforce integration. Some integrations require production credentials or VPN access that is not available locally. Wrapping them in environment checks prevents fatal errors during local development.
Achieving Feature Parity: Memcached, Cron, and File System Constraints
The three areas where VIP diverges most from standard WordPress are object caching, cron scheduling, and filesystem access. Getting these right locally prevents an entire class of deployment failures.
Memcached Object Caching
VIP uses Memcached for object caching in production. The vip dev-env includes a Memcached container and configures WordPress to use it automatically. This means wp_cache_get(), wp_cache_set(), and wp_cache_delete() behave exactly as they would on VIP.
To verify Memcached is working:
vip dev-env exec --slug=acme-corp -- wp cache type
This should output Memcached. If it shows Default, something is wrong with your object cache drop-in.
Here is a practical testing pattern. Say you have a function that caches expensive query results:
function get_featured_events( $count = 10 ) {
$cache_key = 'featured_events_' . $count;
$cached = wp_cache_get( $cache_key, 'events' );
if ( false !== $cached ) {
return $cached;
}
$query = new WP_Query([
'post_type' => 'event',
'posts_per_page' => $count,
'meta_key' => 'is_featured',
'meta_value' => '1',
'orderby' => 'meta_value_num',
'meta_key' => 'event_date',
'order' => 'ASC',
]);
$results = $query->posts;
wp_cache_set( $cache_key, $results, 'events', 3600 );
return $results;
}
Locally with Memcached active, you can verify the caching behavior:
vip dev-env exec --slug=acme-corp -- wp eval "var_dump( wp_cache_get( 'featured_events_10', 'events' ) );"
If the page has been loaded, this returns the cached data. If not, it returns false. This is the exact same behavior you will see on VIP production, which makes local caching bugs reproducible.
Cron Behavior
VIP does not use WordPress’s built-in pseudo-cron (triggered by page visits). Instead, VIP runs cron via a system-level scheduler that fires wp-cron.php at regular intervals. The vip dev-env replicates this by disabling DISABLE_WP_CRON and running cron on a schedule.
To test cron jobs locally, first list scheduled events:
vip dev-env exec --slug=acme-corp -- wp cron event list
To manually trigger a specific event:
vip dev-env exec --slug=acme-corp -- wp cron event run my_custom_cron_hook
To run all due cron events:
vip dev-env exec --slug=acme-corp -- wp cron event run --due-now
One VIP-specific constraint: cron jobs that run longer than 30 seconds are terminated. Your local environment replicates this timeout. If you have a long-running cron job that works on standard WordPress but fails on VIP, this is almost certainly why. The solution is to break the work into smaller batches.
File System Constraints
VIP enforces a read-only file system for everything except /tmp and the uploads directory. You cannot write files to wp-content, create log files in your plugin directory, or generate static HTML caches on disk.
The vip dev-env replicates these restrictions. If your code tries to write outside the allowed directories, it will fail locally just as it would on VIP. This is the single most valuable aspect of dev-env for catching deployment issues.
Common violations I see in code audits:
- Plugins that write compiled CSS or JavaScript to their own directories
- Themes that generate and cache HTML fragments to disk
- Logging libraries that create log files in
wp-content/logs/ - Image manipulation code that saves processed images to the plugin directory
The fix for all of these is the same: use /tmp for temporary files, use Memcached for caching, and use the WordPress uploads directory (via wp_upload_dir()) for any files that need to persist.
For temporary file operations:
$temp_file = tempnam( sys_get_temp_dir(), 'vip_' );
file_put_contents( $temp_file, $data );
// Process the file...
unlink( $temp_file );
For persistent files that users need to access:
$upload_dir = wp_upload_dir();
$file_path = $upload_dir['path'] . '/generated-report.csv';
file_put_contents( $file_path, $csv_data );
SSL and Custom Domains
By default, dev-env provides HTTP access on a generated domain. For projects that require HTTPS (payment integrations, OAuth callbacks, or testing secure cookies), you need additional configuration.
Setting Up HTTPS Locally
The dev-env uses a self-signed certificate. Most browsers will show a warning, which you can bypass for development purposes. In Chrome, you can type thisisunsafe on the warning page to proceed.
For automated testing or API integrations that reject self-signed certificates, you can install the certificate into your system’s trust store. On macOS:
# Find the certificate
vip dev-env exec --slug=acme-corp -- cat /etc/ssl/certs/localhost.crt > /tmp/vip-local.crt
# Add to macOS keychain
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/vip-local.crt
Custom Domain Mapping
For multisite installations or projects that require specific domain names, you can configure custom domains by editing your hosts file:
sudo sh -c 'echo "127.0.0.1 mysite.local" >> /etc/hosts'
Then update the WordPress site URL inside the container:
vip dev-env exec --slug=acme-corp -- wp option update home 'http://mysite.local:8880'
vip dev-env exec --slug=acme-corp -- wp option update siteurl 'http://mysite.local:8880'
Replace 8880 with the actual port shown when you started the environment. The port varies depending on which environments are running and which ports are available.
Troubleshooting Common Issues
After helping dozens of developers set up dev-env, I have seen the same problems repeatedly. Here are the most common ones and their solutions.
Docker Memory Errors
Symptom: Containers crash or restart repeatedly. The docker logs output shows “OOM killed” or the environment simply fails to start.
Cause: Docker Desktop does not have enough memory allocated.
Fix: Increase Docker Desktop’s memory allocation to at least 4 GB. On macOS, go to Docker Desktop, then Preferences, then Resources, then Advanced. On Windows with WSL 2, edit .wslconfig as described in the system requirements section.
You can verify current Docker resource usage:
docker stats --no-stream
This shows memory and CPU usage for all running containers. If any container is consistently near its memory limit, you need to allocate more.
Port Conflicts
Symptom: The environment fails to start with an error about port binding, something like “port is already allocated” or “address already in use.”
Cause: Another service (Apache, Nginx, MAMP, another Docker container) is already using the port that dev-env needs.
Fix: Find what is using the port:
# macOS / Linux
lsof -i :80
lsof -i :443
lsof -i :3306
# Or check all listening ports
sudo lsof -iTCP -sTCP:LISTEN -P
Then either stop the conflicting service or configure dev-env to use a different port. If you are running MAMP or a local Apache, stop those services first. If you have other Docker projects running, you may need to stop them or ensure they use different ports.
Container Network Failures
Symptom: The environment starts, but the site shows “Error establishing a database connection” or WordPress cannot connect to Memcached.
Cause: Docker’s internal networking has become corrupted. This happens more often than you would expect, especially after Docker Desktop updates or system hibernation.
Fix: Reset the Docker networks:
# Stop the environment first
vip dev-env stop --slug=acme-corp
# Prune unused Docker networks
docker network prune -f
# Start the environment again
vip dev-env start --slug=acme-corp
If that does not work, try destroying and recreating the environment:
vip dev-env destroy --slug=acme-corp
vip dev-env create --slug=acme-corp --app-code=/path/to/app
vip dev-env start --slug=acme-corp
You will need to reimport your database after a destroy.
Slow File System Performance on macOS
Symptom: Page loads take 5 to 15 seconds. WP-CLI commands are sluggish. Everything feels like it is running through molasses.
Cause: Docker Desktop’s file sharing between macOS and the container is notoriously slow, especially with the default osxfs/grpc file sharing. Projects with thousands of files (like WordPress with many plugins) are particularly affected.
Fix: In Docker Desktop settings, go to General and enable “Use VirtioFS accelerated directory sharing” if available (Docker Desktop 4.15 or later). VirtioFS provides dramatically better file I/O performance than the older sharing mechanisms.
If VirtioFS is not available on your version, consider upgrading Docker Desktop. The performance difference is substantial: page loads that took 8 seconds with osxfs typically drop to 1 to 2 seconds with VirtioFS.
Stale Container Images
Symptom: After a VIP CLI update, the environment behaves differently or uses outdated WordPress/PHP versions despite your configuration.
Cause: Docker is using cached images from a previous version.
Fix: Update the images:
vip dev-env update --slug=acme-corp
If that does not resolve it, pull the images manually:
docker pull ghcr.io/automattic/vip-container-images/wordpress:latest
docker pull ghcr.io/automattic/vip-container-images/mu-plugins:latest
Then destroy and recreate your environment.
Permission Errors on Linux
Symptom: WordPress shows “permission denied” errors when trying to upload files or access certain directories.
Cause: The user inside the Docker container has a different UID/GID than your host user.
Fix: Ensure your host user’s UID matches the container’s expected UID (usually 33, the www-data user). Alternatively, adjust the permissions on your mounted directories:
sudo chown -R 33:33 /path/to/your-vip-app/uploads
Or, for a less invasive approach, run Docker with your user’s UID:
docker exec -u $(id -u):$(id -g) container_name bash
Comparison with Alternatives
The dev-env tool is not the only way to develop locally for WordPress VIP. Let me compare it honestly with the alternatives.
Local by Flywheel
Local (formerly Local by Flywheel) is a polished desktop application for local WordPress development. It has an excellent GUI, easy site creation, and built-in SSL.
Advantages over dev-env:
- Much easier setup for WordPress development in general
- Beautiful GUI with site switching
- Built-in Mailhog for email testing
- Live Links for sharing sites publicly
- Lower resource consumption for simple setups
Disadvantages for VIP work:
- Does not replicate VIP’s file system constraints
- No Memcached integration (uses Redis or no object cache)
- VIP MU plugins must be installed manually and kept updated
- Cron behavior differs from VIP production
- No built-in VIP CLI integration
Verdict: Local is great for general WordPress development, but for VIP projects, the lack of production parity creates risks. I have seen bugs pass through Local-based testing that would have been caught by dev-env.
DDEV
DDEV is an open-source Docker-based local development tool that supports WordPress (and Drupal, Laravel, and many other frameworks). It is fast, configurable, and has a strong community.
Advantages over dev-env:
- More flexible configuration
- Better performance on some setups (especially with Mutagen file sync)
- Supports many frameworks, not just WordPress
- Excellent add-on ecosystem (Memcached, Elasticsearch, etc.)
- Easier to customize Docker configuration
Disadvantages for VIP work:
- Requires manual configuration to match VIP’s stack
- VIP MU plugins must be installed and updated manually
- File system constraints are not enforced by default
- No VIP CLI integration
- Container images are not aligned with VIP’s production images
Verdict: DDEV is a capable tool, and some teams use it successfully for VIP development by building custom configurations that approximate VIP’s constraints. But “approximate” is the key word. If catching VIP-specific issues early is important (and it should be), dev-env is the safer choice.
Lando
Lando is another Docker-based local development tool with a strong focus on developer experience. It supports WordPress VIP through community-maintained recipes.
Advantages over dev-env:
- Excellent tooling configuration via
.lando.yml - Good proxy support for custom local domains
- Active community with many recipes
- Supports custom service definitions easily
Disadvantages for VIP work:
- VIP recipe is community-maintained, not official
- May lag behind VIP platform changes
- Same manual configuration burden as DDEV
- Higher resource usage than dev-env in my testing
Verdict: Similar to DDEV, Lando can work for VIP development but requires more manual effort to achieve parity. The community VIP recipe is a good starting point but not a replacement for the official tool.
When to Not Use dev-env
There are legitimate cases where dev-env is not the right choice:
- Theme-only development with no VIP-specific code: If you are building a theme that does not use object caching, custom file operations, or VIP-specific APIs, a lighter tool like DDEV or Local is fine.
- Very resource-constrained machines: On laptops with 8 GB of RAM and spinning hard drives, dev-env will be painfully slow. A simpler tool or a remote development environment is better.
- Quick content editing: If you just need to preview content changes, the VIP Dashboard’s preview functionality or a staging environment is faster than spinning up a full local environment.
Team Onboarding Workflow
Getting a single developer set up with dev-env is straightforward. Getting an entire team running the same configuration requires planning.
The Onboarding Document
Every VIP project should have a DEVELOPMENT.md file in the repository root that covers:
- Prerequisites: Exact Docker Desktop version, Node.js version, and any OS-specific requirements.
- Setup commands: Copy-pasteable commands to create and start the environment. Do not make developers think; give them the exact
vip dev-env createcommand with all flags preset. - Database seed: Where to get a recent database dump and how to import it. Ideally, provide a sanitized dump in the repository or on a shared drive, updated weekly.
- Environment variables: Any API keys, service URLs, or configuration values needed for local development.
- Common tasks: How to run tests, build assets, clear caches, and access logs.
- Known issues: Platform-specific gotchas and their workarounds.
Scripting the Setup
I strongly recommend creating a setup.sh script that automates the entire onboarding process:
#!/bin/bash
set -e
SLUG="acme-corp"
APP_CODE="$(cd "$(dirname "$0")" && pwd)"
echo "Checking prerequisites..."
# Check Node.js
if ! command -v node &> /dev/null; then
echo "Node.js is required. Install it via nvm: https://github.com/nvm-sh/nvm"
exit 1
fi
# Check Docker
if ! docker info &> /dev/null 2>&1; then
echo "Docker is not running. Start Docker Desktop first."
exit 1
fi
# Check VIP CLI
if ! command -v vip &> /dev/null; then
echo "Installing VIP CLI..."
npm install -g @automattic/vip
fi
echo "Creating dev environment..."
vip dev-env create
--slug="$SLUG"
--wordpress=latest
--php=8.2
--app-code="$APP_CODE"
echo "Starting environment..."
vip dev-env start --slug="$SLUG"
# Import database if dump exists
if [ -f "$APP_CODE/.data/seed.sql" ]; then
echo "Importing database..."
vip dev-env import sql --slug="$SLUG" "$APP_CODE/.data/seed.sql"
echo "Running search-replace..."
vip dev-env exec --slug="$SLUG" -- wp search-replace
'https://www.acme-corp.com'
"http://${SLUG}.vipdev.lndo.site"
--all-tables
fi
echo ""
echo "Setup complete!"
echo "Access your site at the URL shown above."
echo "Run 'vip dev-env stop --slug=$SLUG' to stop."
Make this script executable and include it in the repository. A new developer should be able to clone the repo, run ./setup.sh, and have a working environment in minutes rather than hours.
Shared Database Dumps
Maintaining a shared, sanitized database dump is one of the highest-leverage things you can do for team productivity. Without it, each developer either works with minimal test data (and misses layout bugs, query performance issues, and edge cases) or goes through a manual export/import process that takes 30 minutes or more.
The ideal workflow:
- A CI/CD job exports the production database weekly.
- The job sanitizes the dump: removes user emails, resets passwords, strips PII (personally identifiable information).
- The sanitized dump is stored in a shared location (S3 bucket, internal file server, or even the repository itself if it is small enough).
- The
setup.shscript downloads and imports this dump automatically.
For sanitization, the WP-CLI search-replace command combined with a custom script works well:
vip dev-env exec --slug=acme-corp -- wp user list --field=ID | while read user_id; do
vip dev-env exec --slug=acme-corp -- wp user update "$user_id"
--user_email="dev+${user_id}@acme-corp.com"
--user_pass="localdev123"
done
This resets all user emails to a predictable pattern and sets a shared password for local development. Never commit real user data to a repository.
Performance Tips for Resource-Constrained Machines
Not everyone develops on a maxed-out MacBook Pro. Here are specific techniques for keeping dev-env usable on machines with limited resources.
Reduce Docker’s Memory Allocation
If you are on a machine with 8 GB of total RAM, allocating 4 GB to Docker (as recommended) leaves very little for your OS, editor, and browser. Try 3 GB as a starting point and increase only if you see out-of-memory errors. Many VIP projects run fine with 3 GB allocated to Docker.
Disable Xdebug When Not Debugging
Xdebug is a PHP debugger that is included in the dev-env containers. When active, it adds significant overhead to every PHP request, typically 2x to 5x slower execution. If you are not actively debugging with breakpoints, disable it:
vip dev-env exec --slug=acme-corp -- bash -c "echo 'xdebug.mode=off' > /usr/local/etc/php/conf.d/99-xdebug-override.ini"
Then restart the PHP service inside the container:
vip dev-env exec --slug=acme-corp -- kill -USR2 1
When you need Xdebug again, remove the override file and restart PHP.
Use a Lighter Code Editor
VS Code is excellent but consumes 500 MB to 1 GB of RAM with typical extensions. If you are tight on resources, consider using a lighter editor for quick changes. Sublime Text uses roughly 100 MB. Vim or Neovim use even less. Save VS Code for when you need its debugging or Git integration features.
Stop Environments When Not in Use
This sounds obvious, but many developers leave environments running for days. Each stopped environment frees 1 to 1.5 GB of RAM immediately. Make a habit of stopping environments at the end of the workday:
vip dev-env stop --slug=acme-corp
Starting an existing environment takes 10 to 30 seconds, so there is minimal friction.
Prune Docker Regularly
Docker accumulates old images, stopped containers, and unused volumes over time. These consume disk space and can slow down Docker operations:
# Remove unused containers, networks, and dangling images
docker system prune -f
# Also remove unused volumes (careful: this removes database data from stopped containers)
docker system prune --volumes -f
# Check disk usage
docker system df
Run docker system prune -f weekly. Only add --volumes if you are comfortable losing data from destroyed environments.
Limit Browser Tabs
Each Chrome tab consumes 50 to 300 MB of RAM depending on the page content. When testing your VIP site locally, close unrelated tabs. Use a dedicated browser profile for development with only essential extensions.
Advanced Configuration: Elasticsearch and Enterprise Search
Some VIP applications use VIP’s Enterprise Search, which is powered by Elasticsearch. The dev-env supports adding Elasticsearch to your local stack.
When creating the environment, you can enable search:
vip dev-env create
--slug=acme-corp
--elasticsearch
Once running, verify Elasticsearch is accessible:
vip dev-env exec --slug=acme-corp -- curl -s http://localhost:9200/_cluster/health | python3 -m json.tool
You should see a JSON response with the cluster status. After that, index your content:
vip dev-env exec --slug=acme-corp -- wp vip-search index --setup
Elasticsearch adds significant resource requirements: roughly 1 GB of additional RAM. Only enable it if your project actually uses Enterprise Search.
Continuous Integration and dev-env
While dev-env is primarily a local tool, some teams integrate it into their CI/CD pipelines for integration testing. The idea is to spin up a dev-env in the CI environment, import a test database, run end-to-end tests, then tear it down.
This is technically possible but has caveats. CI environments typically have limited resources, and Docker-in-Docker (required to run dev-env inside a CI container) adds complexity and overhead. A more practical approach is to use dev-env locally for development and rely on VIP’s staging environments for integration testing.
That said, if you want to use dev-env in CI, the key requirements are:
- A CI environment with Docker support (GitHub Actions, CircleCI, GitLab CI with Docker executor)
- The VIP CLI installed as a build step
- Sufficient memory allocated to the CI runner (8 GB minimum)
- A pre-built database dump for fast setup
A simplified GitHub Actions workflow might look like:
name: Integration Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install -g @automattic/vip
- run: |
vip dev-env create --slug=ci-test --app-code=. --wordpress=latest
vip dev-env start --slug=ci-test
vip dev-env import sql --slug=ci-test .data/test-seed.sql
vip dev-env exec --slug=ci-test -- wp search-replace 'https://www.acme-corp.com' 'http://ci-test.vipdev.lndo.site' --all-tables
- run: |
# Run your tests here
npm run test:e2e
- run: vip dev-env destroy --slug=ci-test
Be aware that this adds significant time to your CI pipeline. The Docker image pull alone can take 2 to 5 minutes. Cache the Docker images between runs to mitigate this.
Working with VIP’s GitHub Integration
VIP applications deploy through GitHub. When you push to a branch, VIP’s infrastructure picks it up and deploys it to the corresponding environment. The local dev-env mirrors this workflow by mounting your Git repository directly.
A recommended branching strategy for VIP projects:
mainormastermaps to the production environmentdevelopmaps to the development environment on VIPpreprodmaps to the pre-production environment- Feature branches for active development, merged into
developvia pull requests
When working locally with dev-env, you develop on feature branches. Because the code is mounted directly, switching branches in your local Git repo immediately changes the code running in the container. No rebuild or restart needed.
This means you can quickly test different branches:
git checkout feature/new-search-page
# Reload the site in your browser: changes are live
git checkout develop
# Back to the develop branch: changes are live immediately
The only time you need to restart the environment is when changing PHP configuration files that are loaded at startup (like vip-config.php constants that affect early initialization).
Debugging Strategies That Leverage dev-env
Having a production-parity environment unlocks debugging techniques that would be dangerous or impossible on live servers.
Query Monitor
Install the Query Monitor plugin in your dev-env to inspect database queries, HTTP API calls, hooks fired, and memory usage:
vip dev-env exec --slug=acme-corp -- wp plugin install query-monitor --activate
Combined with the SAVEQUERIES constant in your local vip-config.php, Query Monitor gives you complete visibility into every database query on every page load. Look for:
- Duplicate queries (the same query running multiple times per page)
- Slow queries (anything over 50 ms deserves investigation)
- Queries missing indexes (Query Monitor highlights these)
- Excessive total query count (VIP recommends keeping pages under 100 queries)
Xdebug Step Debugging
The dev-env containers include Xdebug. To enable step debugging, configure your IDE to listen on port 9003 (the Xdebug 3 default) and set the path mappings between your local project directory and the container’s web root.
For VS Code, add this to your .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/wp-content/client-mu-plugins": "${workspaceFolder}/client-mu-plugins",
"/wp-content/themes": "${workspaceFolder}/themes",
"/wp-content/plugins": "${workspaceFolder}/plugins"
}
}
]
}
For PhpStorm, configure the server with the same path mappings under Settings, then PHP, then Servers.
Once configured, set a breakpoint in your code, start the debug listener in your IDE, and load a page in the browser. Execution will pause at the breakpoint, and you can inspect variables, step through code, and evaluate expressions.
Profiling with Xdebug
Beyond step debugging, Xdebug can generate cachegrind profiles that show exactly where execution time is spent. Enable profiling inside the container:
vip dev-env exec --slug=acme-corp -- bash -c "echo 'xdebug.mode=profile' > /usr/local/etc/php/conf.d/99-xdebug-override.ini && echo 'xdebug.output_dir=/tmp' >> /usr/local/etc/php/conf.d/99-xdebug-override.ini"
Load the page you want to profile, then copy the cachegrind file out of the container:
vip dev-env exec --slug=acme-corp -- ls /tmp/cachegrind.out.*
docker cp $(docker ps -q -f name=acme-corp):/tmp/cachegrind.out.12345 ./profile.cachegrind
Open the file in a tool like KCachegrind (Linux), QCachegrind (macOS via Homebrew), or Webgrind (browser-based). The profile shows function-by-function execution time, making it easy to identify bottlenecks.
Remember to disable profiling when you are done, as it generates large files and slows everything down dramatically.
Managing Environment Updates
The VIP platform and the VIP CLI are updated frequently. Keeping your local environment current requires periodic maintenance.
Updating the VIP CLI
npm update -g @automattic/vip
Check the release notes for breaking changes before updating. The VIP CLI follows semantic versioning, so major version bumps may require configuration changes.
Updating Environment Images
After updating the CLI, update your environment’s container images:
vip dev-env update --slug=acme-corp
This pulls the latest WordPress, PHP, and MU plugin images. Your database and application code are preserved.
Updating VIP MU Plugins
VIP MU Plugins are updated automatically when you update the environment images. But if you need a specific version for testing:
vip dev-env create --slug=test-mu --mu-plugins=/path/to/specific/mu-plugins
This overrides the built-in MU plugins with a local copy. Use this when you need to test against a specific MU plugin version or when contributing fixes back to the MU plugins repository.
Security Considerations for Local Development
Running a local development environment introduces security considerations that are easy to overlook.
Never use production credentials locally. Create separate API keys, database users, and service accounts for local development. If your laptop is compromised, production credentials stored in local configuration files become a liability.
Sanitize imported databases. Production databases contain user emails, passwords (even hashed ones), and potentially sensitive content. Always sanitize before importing locally, as described in the team onboarding section.
Keep Docker Desktop updated. Docker Desktop receives regular security patches. Outdated versions may contain vulnerabilities that could be exploited by malicious container images or network traffic.
Do not expose dev-env to your network. By default, dev-env binds to 127.0.0.1, meaning only your local machine can access it. Do not change this to 0.0.0.0 unless you are on a trusted network and have a specific reason to share access.
Putting It All Together: A Daily Workflow
Here is what a typical development day looks like when using dev-env effectively.
Morning:
# Start your environment
vip dev-env start --slug=acme-corp
# Pull latest code
git pull origin develop
# Check for any pending database migrations
vip dev-env exec --slug=acme-corp -- wp eval "do_action('run_migrations');"
# Clear caches after pulling new code
vip dev-env exec --slug=acme-corp -- wp cache flush
During development:
# Edit code in your IDE; changes are reflected immediately
# Check logs if something breaks
vip dev-env logs --slug=acme-corp
# Run WP-CLI commands as needed
vip dev-env exec --slug=acme-corp -- wp cron event run --due-now
vip dev-env exec --slug=acme-corp -- wp rewrite flush
Before committing:
# Run PHPCS with VIP coding standards
./vendor/bin/phpcs --standard=WordPress-VIP-Go ./client-mu-plugins/ ./themes/
# Run your test suite
vip dev-env exec --slug=acme-corp -- wp eval "require 'tests/run-tests.php';"
# Verify no VIP-restricted functions are used
./vendor/bin/phpcs --standard=WordPressVIPMinimum ./client-mu-plugins/ ./themes/
End of day:
# Stop the environment to free resources
vip dev-env stop --slug=acme-corp
This workflow keeps the environment fresh, catches issues early through VIP-specific linting, and ensures you are not wasting resources when you are not actively developing.
Final Thoughts
The vip dev-env tool has matured significantly since its initial release. It is not perfect. Docker’s resource overhead is real, the initial setup has rough edges, and some features lag behind the actual VIP platform. But the core promise, production parity for local development, delivers genuine value. Bugs that would have surfaced during a VIP code review or, worse, in production after deployment, get caught during local development.
If you are working on a WordPress VIP project and not using vip dev-env, you are flying blind. The constraints that make VIP reliable in production are the same constraints that make standard local environments unreliable for VIP development. Embrace the tool, invest the time in proper setup, and your deployment confidence will improve dramatically.
For teams making the switch from other local development tools, budget half a day for initial setup and configuration, then an hour per additional team member using the onboarding script approach described above. The upfront investment pays for itself the first time you catch a file system constraint violation or a caching bug before it reaches staging.
The WordPress VIP platform continues to evolve, and the dev-env tool evolves with it. Keep your CLI updated, watch the VIP Lobby blog for platform changes, and revisit your local configuration quarterly to ensure you are still matching production as closely as possible.
Marcus Chen
Staff engineer with 12 years in WordPress infrastructure. Previously at Automattic and a large media company. Writes about hosting platforms, caching, and deployment pipelines.