Introduction
Your local WordPress environment is the foundation of your entire development workflow. Get it wrong, and you'll spend days chasing environment-specific bugs that don't exist in production. Get it right, and you'll be spinning up fresh sites, testing plugin conflicts, and collaborating with your team in minutes—not hours. Setting up a local WordPress environment for production means building something that mirrors your actual hosting as closely as possible while staying fast enough that you don't dread running it every morning.
Over the next few sections, we'll walk through the pragmatic choices: which tool to use, how to use it effectively, and how to structure your version control so your codebase stays clean and deployable.
Pick a tool — Local, wp-env or Docker Compose
Before you write a single line of code, you need to decide on your local environment runner. There are three solid options, each with different tradeoffs depending on your team's setup and your project's complexity.
Local by Flywheel
Local is the easiest entry point. It's a GUI application that handles all the Docker complexity behind the scenes. You click "Create Site," pick your PHP version and database, and you're running in 30 seconds. It's perfect if you're working alone or on smaller projects, and the free version is genuinely capable. The downside: you can't easily version-control your site configuration, and spinning up identical environments across a team requires everyone to manually match your settings. It's also heavier on system resources because it runs its own Docker daemon UI.
wp-env — Official Docker-based environment
wp-env is the WordPress.org team's official recommendation, and it's built into the WordPress development toolchain. It's lightweight, version-controllable, and designed specifically for WordPress. Every configuration lives in a .wp-env.json file that you commit to Git. Your entire team runs the exact same environment. The tradeoff: you need to be comfortable with the command line and with JSON configuration. But once it's set up, it becomes a single source of truth.
Docker Compose
Docker Compose is the most flexible option. You write a docker-compose.yml file and get full control over networking, volumes, services, and startup scripts. It's the industry standard for containerized development. The downside: you need to understand Docker, and you're writing more boilerplate. Most teams either skip Docker entirely or jump straight to Compose and deal with the learning curve. It's worth it for larger projects or teams that already use Docker in production.
Our recommendation: Start with wp-env. It's the sweet spot between ease and control. If you need more flexibility later (custom domains, multiple service integrations, non-WordPress containers), migrate to Docker Compose. Local works if you're a solo developer and don't care about team consistency.
wp-env — Official Docker-based environment
Let's focus on wp-env because it's the path most teams should take. The setup takes three steps: install Node (if you don't have it), install wp-env globally, and create your configuration file.
Installation and initial setup
First, install wp-env as a global npm package:
npm install -g @wordpress/envThat's it. You now have the wp-env command available in your terminal. Now create a .wp-env.json file in your project root:
{
"core": "WordPress/WordPress#6.4",
"plugins": [
"."
],
"themes": [],
"phpVersion": "8.2",
"port": 8888,
"env": {
"development": {
"WORDPRESS_DEBUG": true,
"WORDPRESS_DEBUG_DISPLAY": true,
"SCRIPT_DEBUG": true
},
"tests": {
"WORDPRESS_DEBUG": false
}
}
}
This configuration tells wp-env to:
- Use WordPress 6.4 as the core
- Load the current directory (
".") as a plugin - Use PHP 8.2 (adjust based on your production server)
- Run on port 8888
- Enable debug mode in development (you'll see errors immediately)
- Disable debug output in the tests environment
Now start the environment:
wp-env startDocker will pull the images, set up the database, and have you running at localhost:8888 in about 60 seconds. The default login is admin / password.
Practical workflow
Once your environment is running, you interact with it from the terminal. Stop the environment with wp-env stop, restart with wp-env start, and reset to a clean slate with wp-env clean. You edit code in your IDE like normal—the files are mounted directly into the container. Changes to your plugin or theme appear instantly (well, after a page refresh).
If you need to run WP-CLI commands (which we'll cover in the next section), prefix them with wp-env run cli. For example:
wp-env run cli wp option get siteurlThis isolation is powerful. Your development database is completely separate from production. You can test destructive migrations, experiment with data structures, and blow everything away without consequences. When you're done for the day, wp-env stop frees up system resources. Your team member runs the same three commands and has an identical environment.
WP-CLI — Manage sites from the terminal
WP-CLI is the command-line interface for WordPress. It lets you do everything the admin panel does—plus things the admin panel can't do—from the terminal. This is essential for reproducible development and automation.
Essential WP-CLI commands
With wp-env, you run WP-CLI commands by prefixing them with wp-env run cli. Here are the commands you'll use constantly:
Database and site setup:
wp-env run cli wp db export dump.sql
wp-env run cli wp option get siteurl
wp-env run cli wp option set siteurl http://localhost:8888
Plugin and theme management:
wp-env run cli wp plugin activate my-plugin
wp-env run cli wp plugin deactivate my-plugin
wp-env run cli wp theme activate my-theme
wp-env run cli wp theme list
User and content management:
wp-env run cli wp user create testuser test@example.com --user_pass=testpass --role=editor
wp-env run cli wp post create --post_type=post --post_title="Test Post" --post_status=publish
wp-env run cli wp db query "SELECT COUNT(*) FROM wp_posts"
The real power emerges when you combine these into scripts. Suppose your team needs to onboard a new developer. Instead of walking them through five manual steps, you write a setup script:
#!/bin/bash
wp-env start
wp-env run cli wp plugin activate my-plugin
wp-env run cli wp user create devuser dev@company.com --user_pass=devpass --role=editor
wp-env run cli wp option set siteurl http://localhost:8888
echo "Environment ready at http://localhost:8888"
echo "Login: devuser / devpass"
One command, one minute, and the developer is up and running. No guesswork, no forgotten steps.
Searching and debugging with WP-CLI
WP-CLI also shines for debugging. Need to find where a specific option is set? Search the database:
wp-env run cli wp option get --format=json
Need to see all hooks a plugin registers? Use wp hook list. Need to profile a query? Export and analyze with WP-CLI's built-in performance tools. This is how you catch slow queries and missing indices before they hit production.
Version control — Track your theme and plugins in Git, not the whole site
This is where many teams go wrong. The temptation is to commit your entire WordPress installation—core, plugins, themes, uploads folder, everything—to Git. Resist this. It's a versioning nightmare and a waste of disk space.
Structure your repository correctly
Your Git repository should contain only your custom code:
my-plugin/
├── .gitignore
├── .wp-env.json
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ ├── index.php
│ ├── class-my-plugin.php
│ └── admin/
├── tests/
│ ├── bootstrap.php
│ └── test-my-plugin.php
├── phpcs.xml
├── composer.json
└── package.json
Your .gitignore should exclude everything that isn't yours:
# WordPress core and dependencies
/wordpress/
/wp-content/
vendor/
node_modules/
# wp-env generated files
.wp-env
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
# Logs and temp
*.log
debug.log
Now your repository is lean, portable, and version-controlled. A teammate clones it, runs wp-env start, and they have your entire development environment. No merge conflicts from WordPress core updates. No massive diffs from plugin dependencies.
Dependencies and composer.json
If your plugin depends on external libraries, use Composer. This is non-negotiable for production WordPress work:
{
"name": "company/my-plugin",
"type": "wordpress-plugin",
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"wp-coding-standards/wpcs": "^3.0"
},
"scripts": {
"test": "phpunit",
"lint": "phpcs --standard=WordPress"
}
}
Run composer install after cloning. Your dependencies are locked to specific versions (via composer.lock), and every developer, every CI pipeline, and your production server install the same versions. This eliminates "works on my machine" bugs.
The .wp-env.json as your contract
Your .wp-env.json file is part of your repository. When you update PHP version, WordPress core version, or add a test database plugin, you commit that change. The next time a teammate pulls your branch, they run wp-env start, and they get the exact environment you intended. This is crucial for onboarding, CI/CD, and preventing drift between developers.
Common mistakes
Mistake 1: Using different PHP versions locally and in production. If your production server runs PHP 8.1 and you're developing on PHP 8.2, you'll encounter type compatibility bugs that only surface when you deploy. Always match your local php version to your production server. Check your hosting provider's documentation, set it in .wp-env.json, and commit that choice.
Mistake 2: Committing wp-content/plugins and wp-content/themes to Git. WordPress core and third-party plugins are dependencies, not source code. Your composer.json or .wp-env.json is your dependency declaration. When a plugin updates, you'll either see a commit that adds 50 MB of files, or you'll have a merge conflict nightmare. Track only your custom plugins and themes.
Mistake 3: Not using debug mode locally. The WORDPRESS_DEBUG and SCRIPT_DEBUG constants catch deprecations, notices, and warnings that otherwise go silent. Enable them in development (as shown in our .wp-env.json example) and disable them in production. You'll catch bugs before your users do.
Mistake 4: Hardcoding localhost URLs. Your plugin or theme shouldn't assume it's running at localhost:8888. Use home_url(), admin_url(), and wp_localize_script() to pass URLs to JavaScript. This ensures the same code works in development and production without modification.
Mistake 5: Ignoring Docker or environment inconsistencies. "It works on my machine" is a sign that your environment setup isn't repeatable. If your team uses Local, someone else uses Docker, and another uses XAMPP, you'll have environment-specific bugs. Pick one tool per team and document it in your README.
Key takeaways
- Start with wp-env. It's lightweight, version-controllable, and the official WordPress recommendation. Move to Docker Compose only if you need features wp-env doesn't provide.
- Use WP-CLI for all repetitive tasks. Write setup scripts so new developers can bootstrap in one command instead of five manual steps.
- Version-control only your custom code (plugins, themes, configuration). Let Composer or wp-env handle WordPress core and dependencies.
- Match your local PHP version to production. Test with debug mode enabled. Use
composer.jsonto lock dependency versions. - Commit your
.wp-env.json(ordocker-compose.yml) to Git. It's your environment contract. When it changes, teammates know they need to restart their local setup. - Automate environment setup. If it requires more than three terminal commands, write a script.






Comments
Loading comments…