Isolated, production-like WordPress on Windows
Ditch XAMPP for a consistent, isolated, production-like WordPress development environment on Windows using Docker Desktop.
Developing WordPress sites locally on Windows has traditionally involved tools like XAMPP or WAMP. While they get the job done, they can sometimes be clunky, lead to the dreaded "it works on my machine!" syndrome, or create conflicts when juggling multiple projects with different server requirements. It's quite useful, actually, to find a better way. If you're a Windows developer looking for a more robust, consistent, and modern local development setup for WordPress, it's time to embrace Docker.
This guide will walk you through setting up WordPress locally on your Windows machine using Docker Desktop, covering everything from initial setup to advanced configurations and troubleshooting common issues. We'll make your development environment isolated, portable, and much closer to a production setup.
Heads up This guide is Windows-and-Docker-Desktop specific and still works, but in 2026 the
fastest route to a containerised WordPress stack is DDEV — it wraps Docker with
WordPress-aware presets (ddev config --project-type=wordpress then ddev start), handles
HTTPS and hostnames for you, and runs the same on Windows, macOS and Linux. Treat the hand-rolled
docker-compose.yml below as the useful "know what's under the hood" version.
Docker "containerizes" applications, meaning it packages an application and all its dependencies (like PHP, MySQL, Apache/Nginx) into a standardized unit. For WordPress development on Windows, this offers several key advantages:
docker-compose.yml file, can be easily
shared with team members or used across different machines.Let's build your local WordPress environment piece by piece.
Docker Compose is a tool for defining and running multi-container Docker applications. We'll use a
docker-compose.yml file to define our WordPress site and its database.
Create a new folder for your WordPress project anywhere on your system (e.g.,
C:\Users\YourUser\Projects\MyWordPressSite).
docker-compose.yml:Inside your project directory, create a file named docker-compose.yml and add the following:
version: '3.8'
services:
db:
image: mariadb:10.11 # Or your preferred MariaDB/MySQL version
container_name: my_wordpress_db
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_strong_root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress_user
MYSQL_PASSWORD: your_strong_user_password
networks:
- wp_network
wordpress:
image: wordpress:latest # Official WordPress image
container_name: my_wordpress_app
volumes:
- wp_data:/var/www/html # For WordPress core, themes, plugins
ports:
- "80:80" # Maps host port 80 to container port 80
restart: always
environment:
WORDPRESS_DB_HOST: db:3306 # Service name 'db' and its internal port
WORDPRESS_DB_USER: wordpress_user
WORDPRESS_DB_PASSWORD: your_strong_user_password
WORDPRESS_DB_NAME: wordpress
depends_on:
- db
networks:
- wp_network
volumes:
db_data:
wp_data:
networks:
wp_network:
driver: bridge
Heads up Two changes for 2026. First, drop the top version: '3.8' line entirely —
the Compose Specification made it obsolete and current Docker Compose (v2) prints a warning when it sees it. Second,
mariadb:10.11 still works but is ageing; pin to mariadb:11.4 (the current LTS) for a fresh
build. Everything else in the file is unchanged.
Explanation of docker-compose.yml:
services: Defines the containers.
db: Our MariaDB database.image: Specifies the MariaDB version.volumes: db_data:/var/lib/mysql creates a persistent named volume
db_data to store your database files. This ensures your data isn't lost when the container
stops.environment: Sets up database credentials. Change these passwords!wordpress: Our WordPress application.image: Uses the latest official WordPress image.volumes: wp_data:/var/www/html creates a persistent named volume
wp_data. Initially, this is where WordPress files live. We'll change this later for easier
development.ports: - "80:80" maps port 80 on your Windows machine (host) to port 80 inside the
WordPress container.environment: Configures WordPress to connect to the db service.depends_on: Ensures the db service starts before the wordpress
service.volumes: Declares the named volumes for data persistence.networks: Creates a custom bridge network wp_network for our services to
communicate.Open your terminal (PowerShell, Command Prompt, or Windows Terminal) in your project directory and run:
docker compose up -d
The -d flag runs the containers in detached mode (in the background). Docker will download the images
(if you don't have them already) and start the containers.
Open your web browser and go to http://localhost. You should see the WordPress "famous five-minute installation" screen. Complete the setup.
If you want to run multiple WordPress sites (or other web projects) simultaneously, they can't all use port 80 on your host machine. You need to map different host ports to each project's container port 80.
In your docker-compose.yml for the wordpress service, change the ports section:
For Project 1 (already done, using port 80):
ports:
- "80:80" # http://localhost
For Project 2 (in a new project folder with its own docker-compose.yml):
ports:
- "8080:80" # http://localhost:8080
For Project 3:
ports:
- "8001:80" # http://localhost:8001
And so on. Just pick a host port that isn't already in use.
phpMyAdmin is a popular web interface for managing MySQL/MariaDB databases. Let's add it to our setup.
Edit your docker-compose.yml and add a new service for phpmyadmin:
services:
db:
# ... (your existing db configuration) ...
wordpress:
# ... (your existing wordpress configuration) ...
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: my_wordpress_phpmyadmin
restart: always
ports:
- "8008:80" # Access phpMyAdmin on host port 8008 (choose an available port)
environment:
PMA_HOST: db # Tells phpMyAdmin the hostname of the database service ('db')
PMA_PORT: 3306 # The internal port of the database service
# MYSQL_ROOT_PASSWORD: your_strong_root_password # Optional: if you want to login as root
depends_on:
- db
networks:
- wp_network
phpmyadmin is on the same wp_network.docker compose up -d again to create and start the new phpMyAdmin container.db. Use the wordpress_user and
your_strong_user_password (or root credentials) to log in.
The named volume wp_data is good for persistence, but not ideal for direct theme/plugin development
from your Windows file system. For that, we use bind mounts. A bind mount maps a directory on your
host machine directly into the container.
Inside your project directory (e.g., C:\Users\YourUser\Projects\MyWordPressSite), create a new folder
named wordpress_files.
docker-compose.yml:Change the volumes section for your wordpress service:
services:
wordpress:
image: wordpress:latest
container_name: my_wordpress_app
volumes:
# Replace the named volume with a bind mount:
- ./wordpress_files:/var/www/html
ports:
- "80:80" # Or your chosen port
# ... (rest of your wordpress service configuration)
The ./wordpress_files:/var/www/html line maps the wordpress_files folder (relative to
your docker-compose.yml) to /var/www/html inside the container.
wp_data volume, you might want to remove it to avoid conflicts or copy its
contents to your new wordpress_files folder. To remove the old volume (this will delete its data!):
docker volume rm MyWordPressSite_wp_data (the name might vary, check with
docker volume ls).docker compose up -d --force-recreate. The --force-recreate flag ensures the
container is rebuilt with the new volume mapping../wordpress_files folder.
Now, any changes you make in C:\Users\YourUser\Projects\MyWordPressSite\wordpress_files will be
instantly reflected in the container, making theme and plugin development a breeze!
We'll cover this in troubleshooting, but for best performance, your project files (including
wordpress_files) should ideally live inside your WSL 2 filesystem, not directly on the Windows
C: drive.
Need to quickly show your local site to a client? ngrok creates a secure tunnel from a public URL to your local machine.
ngrok.exe somewhere accessible (or add it to your PATH).ngrok authtoken YOUR_AUTHTOKEN
Heads up The ngrok authtoken command is deprecated. On current ngrok (v3) use
ngrok config add-authtoken YOUR_AUTHTOKEN instead — it writes the token to your ngrok config file. The
ngrok http 80 command below is unchanged.
If your WordPress site is running locally on http://localhost:80 (because of the 80:80
mapping in Docker Compose), run:
ngrok http 80
If you changed the host port (e.g., to 8080:80), use that port:
ngrok http 8080
ngrok will display a public URL (e.g., https://random-string.ngrok-free.app).
http://localhost/wp-admin).https://<random-string>.ngrok-free.app URL provided by ngrok. Do not include a trailing
slash.http://localhost (or your local port) in the WordPress settings.You might encounter a few bumps along the road. Here's how to tackle them:
Problem: Another application on your Windows machine (like IIS, Skype, or another web server) is already using the host port you're trying to map.
Solution 1 (Find & Stop):
netstat -ano | findstr ":80" (replace :80 if it's a different port).
Solution 2 (Change Docker Port): Modify the host port in your docker-compose.yml for
the wordpress service (e.g., ports: - "8081:80") and access your site at
http://localhost:8081. Then run docker compose up -d --force-recreate.
Problem: Docker is having trouble writing to its data directories or your volume mounts.
Solutions:
docker system prune -a.
(Use docker system prune -a --volumes with extreme caution, as it removes unused named volumes).wsl --shutdown, wait a few seconds, then restart
Docker Desktop.
Problem: Almost always a CPU architecture mismatch (e.g., trying to run an amd64 image
on an arm64 machine like Apple Silicon or Windows on ARM, or vice-versa, without proper emulation or the
correct image variant).
Solution:
docker compose downdocker rmi wordpress:latest mariadb:10.11
phpmyadmin:latest).docker compose pull (Docker should pick the correct architecture).docker compose up -d --force-recreate.localhost Access (The WSL 2 Filesystem Bottleneck)
Problem: If your project files (the bind mount like ./wordpress_files) are on your
Windows filesystem (e.g., C: drive), accessing them from the WordPress container (running in WSL 2) is slow due to
the filesystem boundary crossing. WordPress reads many PHP files on each request, leading to sluggish performance.
Solution (Highly Recommended for Performance): Store project files inside the WSL 2 filesystem.
wsl in PowerShell or Command Prompt.mkdir -p ~/projects/my_wp_site and
cd ~/projects/my_wp_site.wordpress_files from Windows (e.g.,
/mnt/c/Users/YourUser/Projects/MyWordPressSite/wordpress_files) into this WSL directory.# Inside WSL terminal
cp -r /mnt/c/Users/YourUser/Projects/MyWordPressSite/wordpress_files ./
docker-compose.yml:
docker-compose.yml into the WSL project directory (e.g.,
~/projects/my_wp_site/docker-compose.yml).volumes:
- ./wordpress_files:/var/www/html
docker-compose.yml stays on Windows, use the //wsl$/DISTRO_NAME/path/to/files
format (e.g., //wsl$/Ubuntu/home/your_wsl_user/projects/my_wp_site/wordpress_files:/var/www/html).docker compose down and docker compose up -d --force-recreate from the directory
containing your (potentially updated) docker-compose.yml.docker logs my_wordpress_phpmyadmin (or your container name).PMA_HOST: Ensure it's set to db (or your database service name)
in docker-compose.yml.db container (docker logs
my_wordpress_db). phpMyAdmin can't connect if the database itself isn't running correctly.
Solution: Add the UPLOAD_LIMIT environment variable to your phpmyadmin
service in docker-compose.yml:
environment:
PMA_HOST: db
PMA_PORT: 3306
UPLOAD_LIMIT: 256M # Or your desired size (e.g., 1G)
Then run docker compose up -d --force-recreate phpmyadmin.
While Docker offers immense flexibility and consistency, it's worth knowing other popular local development tools for WordPress on Windows:
Docker sits as a more powerful, developer-focused solution that gives you fine-grained control over your entire stack, making it an industry standard for many development workflows beyond just WordPress.
2026 update If you want the container isolation without hand-writing Compose files,
DDEV has become the go-to for local WordPress and now sits alongside LocalWP as the standard
developer choice — it uses Docker under the hood but gives you per-project PHP/database versions, trusted HTTPS and
ddev import-db/ddev export-db helpers for free. The raw Docker approach here is still the
best way to understand what any of these tools are actually doing.
Moving your local WordPress development on Windows to Docker might seem like a bit of a learning curve initially, but the benefits in terms of consistency, isolation, flexibility, and cleaner project management are well worth it. You'll spend less time fighting environment issues and more time building awesome WordPress sites.
This guide has covered the essentials to get you up and running, along with solutions to common pain points. Happy Dockerizing!
I help content-rich organisations understand their data and pull it back into one place. Tell me what specific problems you're having and I'll be happy to talk solutions with you.
Tell me what specific problems you’re having and I’ll be happy to talk solutions with you.