14+ years building on WordPress / Replies in under 5 hours
Development 8 min read · Updated July 2026

Setting Up a Modern Local WordPress Development Environment (Docker, Lando & LocalWP)

Photo of Ajay Khandal
Ajay Khandal
WordPress Developer
Setting Up a Modern Local WordPress Development Environment (Docker, Lando & LocalWP)
TL;DR

Containerized local WordPress environments — Docker Compose, Lando, or LocalWP — package PHP, MySQL, and WordPress into an isolated unit that matches staging and production exactly, eliminating "works on my machine" failures. Docker gives full control via <code>docker-compose.yml</code>; Lando adds WordPress-specific shortcuts like <code>lando wp</code> and <code>lando pull</code>; LocalWP offers a no-CLI GUI for the fastest setup. Commit your config file to the repo so every developer shares the same environment.

MAMP and WAMP served their era well, but they share a fundamental flaw: the PHP version, MySQL configuration, and server settings on your laptop have nothing to do with what runs in production. A containerized local environment packages the application, its runtime, and its configuration into a single unit — one that is identical everywhere it runs. This is what eliminates the “works on my machine” failure mode and what makes your local environment a reliable preview of staging and prod.

Why Containers Beat Traditional Local Servers

Three properties make containerized environments the right default for WordPress development today:

  • Environmental parity. The PHP version, MySQL configuration, and server software defined in your docker-compose.yml or .lando.yml are exactly what runs on every machine that clones the project — yours, a teammate’s, and CI.
  • Per-project isolation. Project A can run PHP 8.2 while Project B stays on PHP 7.4 with no conflict. Each project’s dependencies are completely sandboxed.
  • Config-as-code portability. The environment is a text file committed to the repository. Onboarding a new developer means cloning the repo and running one command — no “install this extension, then change that ini setting” handoff document.

Option 1: Pure Docker with Docker Compose

Docker gives you the most control and the most transferable knowledge — the same docker-compose.yml pattern works for any stack, not just WordPress. The trade-off is a steeper initial setup. The version: key in Compose files is deprecated as of Docker Compose v2; omit it in new projects.

# docker-compose.yml — no "version:" key needed in Compose v2+
services:
  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: wordpress_db
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: wp_password
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    depends_on:
      - db
    image: wordpress:php8.2-apache
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress_db
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: wp_password
      WORDPRESS_DEBUG: 1
    volumes:
      - ./wp-content:/var/www/html/wp-content  # mount only your code

  mailpit:
    image: axllent/mailpit
    ports:
      - "8025:8025"  # web UI
      - "1025:1025"  # SMTP

volumes:
  db_data:

The mailpit service captures all outbound email from WordPress inside a local web UI at http://localhost:8025 — no email actually leaves your machine. Wire it up in wp-config-local.php:

# wp-config-local.php — add after WordPress DB constants
define( 'SMTP_HOST', 'mailpit' );   // container name is the hostname
define( 'SMTP_PORT', 1025 );
define( 'SMTP_FROM', 'wp@localhost' );

Starting the Environment and Running WP-CLI

# Start all containers in the background
docker compose up -d

# Tail logs (useful on first run to watch MySQL init)
docker compose logs -f

# Stop without destroying data
docker compose stop

# Stop and remove containers + volumes (full reset)
docker compose down -v

WP-CLI is not included in the official WordPress Docker image by default. You can either add a wpcli service to your compose file, or run commands via docker compose exec:

# Install WordPress core after containers are running
docker compose exec wordpress wp core install   --url="http://localhost:8080"   --title="My Local Site"   --admin_user=admin   --admin_password=password   [email protected]   --allow-root

# Import a production DB dump
docker compose exec -T db mysql -u wp_user -pwp_password wordpress_db   < prod-backup.sql

# Search-replace the prod URL with the local one
docker compose exec wordpress wp search-replace   "https://example.com" "http://localhost:8080"   --allow-root --precise

The search-replace step is the one most developers forget when importing a production database locally — without it, every URL in the database still points at the live site and WordPress redirects you out of your local install on every click.

Option 2: Lando

Lando is a Docker wrapper built specifically for development workflows. It handles the container networking, SSL certificates, and tool integrations that you would otherwise configure manually. A single .lando.yml replaces several hundred lines of Docker configuration for a typical WordPress project.

# .lando.yml
name: myproject
recipe: wordpress
config:
  php: "8.2"
  webroot: .
  database: mysql:8.0
  xdebug: true       # enable Xdebug 3 in one line
  config:
    php: .lando/php.ini   # optional custom php.ini

tooling:
  phpunit:
    service: appserver
    cmd: ./vendor/bin/phpunit
; .lando/php.ini — custom PHP settings applied inside the appserver container
upload_max_filesize = 128M
post_max_size       = 128M
memory_limit        = 512M
max_execution_time  = 300
# Initialise a new project (run once in an empty folder)
lando init --recipe wordpress

# Start all containers
lando start

# Run WP-CLI commands directly (no exec needed)
lando wp core install   --url="https://myproject.lndo.site"   --title="My Local Site"   --admin_user=admin   --admin_password=password   [email protected]

# Pull the database and uploads from a Pantheon/Kinsta/Acquia environment
lando pull --database=dev --files=dev

# Share your local site publicly via a tunnel (useful for client demos)
lando share

lando pull is the standout feature for client work — it downloads the database and file uploads from a connected hosting provider (Pantheon, Kinsta, Acquia) and runs the URL search-replace automatically. What would otherwise be a manual multi-step process becomes one command. For managing PHP dependencies inside the container, see the Composer for WordPress guide — Lando exposes lando composer the same way it exposes lando wp.

Option 3: LocalWP

LocalWP requires no command-line knowledge and no configuration files. Download the app, click “Create a new site,” choose a PHP version, and the site is running inside a .local domain in under two minutes. It is the fastest path from zero to a working WordPress install.

Key features worth knowing:

  • Live Links — expose your local site publicly via a temporary URL for client demos or webhook testing.
  • Blueprints — save any site (with plugins, theme, and settings pre-configured) as a template. Spinning up a new client project takes seconds.
  • Built-in WP-CLI and SSH. Every site has a “Shell” tab that drops you into the container’s terminal:
# LocalWP bundles its own WP-CLI — open the site's "Shell" tab and run:
wp plugin install woocommerce --activate
wp search-replace "https://example.com" "http://mysite.local" --precise

# Export the DB for use on staging
wp db export --add-drop-table ~/Desktop/local-backup.sql

LocalWP’s main limitation is its hosting integrations: the one-click pull/push feature works natively only with WP Engine and Flywheel sites. If your production host is elsewhere, you manage database imports manually.

Comparison: Which Tool Fits Your Workflow?

LocalWP Lando Pure Docker
Learning curve Lowest (GUI) Medium (YAML + CLI) Highest (full CLI)
Setup time ~2 min (click & go) ~5 min 15–30 min (first time)
Per-project PHP version Yes (dropdown) Yes (php: "8.x") Yes (image tag)
Team portability Good (share folder) Excellent (.lando.yml) Excellent (docker-compose.yml)
Xdebug Toggle in UI xdebug: true in YAML Manual env vars
Email capture Built-in (MailHog) Add Mailhog service Add Mailpit service
DB pull from hosting Flywheel/WP Engine only Pantheon, Kinsta, Acquia Manual import
Best for Freelancers, designers Agencies, complex projects DevOps, custom stacks

The short version: reach for LocalWP when speed matters more than configurability. Use Lando when you need the environment definition committed to the repo and shared across a team. Use pure Docker when you need to match a non-standard production stack exactly, or when the project already has Docker infrastructure the local environment should mirror.

Adding Xdebug to a Docker Environment

Step debugging is the fastest way to understand what WordPress is actually doing inside a hook or filter. Xdebug 3 requires only a few environment variables added to the wordpress service in your compose file:

# docker-compose.yml — add an xdebug service override
  wordpress:
    environment:
      XDEBUG_MODE: develop,debug
      XDEBUG_CONFIG: >-
        client_host=host.docker.internal
        client_port=9003
        start_with_request=yes
    extra_hosts:
      - "host.docker.internal:host-gateway"  # Linux only; Mac/Win handle this automatically

With this in place, set a breakpoint in VS Code (with the PHP Debug extension) or PhpStorm and refresh the browser — execution pauses at the breakpoint with full variable inspection. Lando’s xdebug: true config option wires the same thing up automatically without the manual env vars. For the testing layer that pairs with step debugging, see the WordPress PHPUnit testing guide.

Local to Staging to Production Workflow

A containerized local environment is the first step in a complete deployment pipeline. The standard flow: develop locally → push to a feature branch → CI deploys to staging automatically → merge to main triggers a production deploy. For the full CI/CD setup, see the WordPress CI/CD pipeline guide. For creating and managing a staging environment, see the WordPress staging site guide.

The key discipline is keeping the staging and production environments as close as possible to local — which is exactly what defining the environment in a committed config file enforces. If staging runs PHP 8.2, your .lando.yml should say php: "8.2". The containerized local setup makes that easy to verify and enforce across the team.

Quick-Reference Checklist

  • Drop the version: key from new docker-compose.yml files — it is deprecated in Compose v2.
  • Mount only ./wp-content, not the full wp-content/WordPress root — keeps the image slim and avoids permission issues.
  • Add a Mailpit or MailHog service to every local stack — never send real email from a dev environment.
  • Always run wp search-replace after importing a production database locally.
  • Commit .lando.yml or docker-compose.yml to the repository — the environment definition is part of the codebase.
  • Use lando pull for staging/prod DB imports on supported hosts; use docker compose exec for manual imports elsewhere.
  • Enable Xdebug via xdebug: true in Lando or via XDEBUG_MODE env vars in Docker — step debugging saves hours.

Frequently asked questions

It depends on your workflow. LocalWP is the fastest to set up (under two minutes, no CLI) and is ideal for freelancers or designers who need a quick local site. Lando is the best choice for teams and agency developers — the .lando.yml config file is committed to the repo so every developer shares the exact same environment. Pure Docker gives you the most control and is the right choice when you need to match a non-standard production stack or integrate with existing Docker infrastructure.

Use docker compose exec wordpress wp <command> --allow-root. The wordpress is the service name from your docker-compose.yml, and --allow-root is required because the process inside the container typically runs as root. For example: docker compose exec wordpress wp plugin list --allow-root. Lando users can simply run lando wp <command> without the exec wrapper — Lando proxies the command into the correct container automatically.

Yes — this is one of the core advantages of container-based environments over MAMP/WAMP. Each project's docker-compose.yml or .lando.yml specifies its own PHP version (e.g. image: wordpress:php8.2-apache in Docker, or php: "7.4" in Lando). Since each project runs in isolated containers, there is no conflict between them regardless of how many are running simultaneously.

Export the database from production (using wp db export, phpMyAdmin, or your host's backup tool), then run: docker compose exec -T db mysql -u wp_user -pwp_password wordpress_db < prod-backup.sql. After importing, always run a search-replace to swap the production URL for your local one: docker compose exec wordpress wp search-replace 'https://example.com' 'http://localhost:8080' --allow-root --precise. Skipping the search-replace causes WordPress to redirect every request back to the live site.

LocalWP uses a container-based architecture but not Docker Desktop directly — it bundles its own lightweight container runtime. This is why LocalWP does not require Docker Desktop to be installed and why its environments do not appear in docker ps. The trade-off is that LocalWP environments are not portable via a config file the way a docker-compose.yml or .lando.yml is — you share LocalWP sites by exporting and importing the full site package.

Add a Mailpit or MailHog container to your docker-compose.yml and configure WordPress to use it as the SMTP server. Mailpit runs a local SMTP server (port 1025) and a web UI (port 8025) that displays every outgoing email. Because the SMTP server is a local container, no email escapes to the internet. Lando includes MailHog by default on its WordPress recipe — emails sent from your Lando site are automatically captured at http://mail.myproject.lndo.site without any extra configuration.

Photo of Ajay Khandal

Written by Ajay Khandal

I'm a freelance WordPress developer with 14+ years of experience building, fixing, and speeding up sites for businesses, agencies, and store owners across the US, UK, Europe, and Australia. I specialize in custom themes, WooCommerce, and performance — the kind of work that shows up as faster load times and fewer support tickets. No account managers, no outsourced tickets — you work directly with me, with replies typically inside 5 hours.

Work with me →