Docker for WordPress Development: A Practical Guide

Docker for WordPress Development: A Practical Guide

Why Docker for WordPress Development

The phrase “works on my machine” has killed more release schedules than any bug ever could. When a team of engineers spreads across time zones — each running different PHP versions, different MySQL releases, different OS-level libraries — debugging becomes an exercise in environment archaeology rather than software engineering.

Docker solves this at the root. A docker wordpress setup encapsulates every dependency — PHP-FPM version, web server config, database engine, cache layer — inside a declarative docker-compose.yml file that every developer on the team runs identically. Onboarding a new engineer drops from a half-day affair to a single command.

Beyond parity, Docker gives WordPress developers three compounding advantages: isolated environments per project (no port conflicts, no global PHP installs), fast teardown and rebuild cycles, and a direct path to production parity when the same images are promoted through staging and prod.

Anatomy of a Docker WordPress Stack

A production-realistic local stack for WordPress typically comprises five services. Each runs in its own container and communicates over a shared Docker network.

Service Image Role
WordPress (PHP-FPM) wordpress:php8.2-fpm Executes PHP; serves WordPress application code
Web Server nginx:alpine Handles HTTP, proxies .php requests to PHP-FPM
Database mariadb:10.11 Persistent MySQL-compatible storage for WP data
Cache redis:7-alpine Object cache (via Redis Object Cache plugin)
Mail Catcher mailhog/mailhog Intercepts outbound email; web UI on port 8025

An optional sixth service — wordpress:cli — lets you run WP-CLI commands inside the container without installing anything locally. More on that below.

A Minimal docker-compose.yml

The following compose file wires together the core services. It is intentionally readable rather than exhaustive — add what your project needs, remove what it does not.

version: "3.9"

services:
  db:
    image: mariadb:10.11
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: ${DB_NAME}
      MARIADB_USER: ${DB_USER}
      MARIADB_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  wordpress:
    image: wordpress:php8.2-fpm
    restart: unless-stopped
    depends_on:
      - db
      - redis
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: ${DB_NAME}
      WORDPRESS_DB_USER: ${DB_USER}
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_TABLE_PREFIX: wp_
    volumes:
      - ./wp-content:/var/www/html/wp-content

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    depends_on:
      - wordpress
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./wp-content:/var/www/html/wp-content:ro

  mailhog:
    image: mailhog/mailhog
    ports:
      - "8025:8025"

volumes:
  db_data:

Store secrets in a .env file at the project root and add it to .gitignore. Docker Compose reads .env automatically.

Volumes: Keeping Code and Data Alive

Two types of mounts matter here. Bind mounts (the ./wp-content lines above) map a host directory into the container so your editor writes directly to the files PHP-FPM serves. Named volumes (db_data) let Docker manage the database files on the host — they survive docker compose down but are destroyed by docker compose down -v, which is useful for a clean reset.

Keep your theme, plugins, and uploads in ./wp-content under version control (excluding uploads/ from Git). Let Docker own the WordPress core files inside the container. This separation means updating WordPress is a one-line image tag change.

Environment Configuration

A .env.example file committed to the repo documents every variable without exposing real credentials. Developers copy it to .env on first clone. At minimum, define:

  • DB_ROOT_PASSWORD — MariaDB root password (local only, can be simple)
  • DB_NAME, DB_USER, DB_PASSWORD — application database credentials
  • WP_ENV — environment flag (development / staging / production)
  • WP_HOME / WP_SITEURL — base URLs; avoids hardcoding in wp-config.php

For staging and production, the same variable names map to secrets injected via CI/CD pipelines or cloud secret managers — keeping docker-compose.yml identical across environments and letting configuration drive behavior.

Using WP-CLI Inside Containers

WP-CLI is indispensable for WordPress automation: installing plugins, running database migrations, flushing caches, managing users. With Docker, you run it as a one-off container rather than a persistent service:

# Install a plugin
docker compose run --rm wpcli plugin install redis-cache --activate

# Run a database search-replace after cloning a prod DB
docker compose run --rm wpcli search-replace 'https://example.com' 'http://localhost:8080'

# Flush object cache
docker compose run --rm wpcli cache flush

Add the WP-CLI service to your compose file:

  wpcli:
    image: wordpress:cli
    depends_on:
      - wordpress
      - db
    volumes:
      - ./wp-content:/var/www/html/wp-content
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: ${DB_NAME}
      WORDPRESS_DB_USER: ${DB_USER}
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}

Vilee LLC combines deep technical expertise in WordPress/WooCommerce development with AI-powered automation to operate 520+ profitable online businesses at scale.

Common Pitfalls and How to Avoid Them

File Permission Conflicts

The www-data user inside the WordPress container (UID 33 on Debian-based images) may not match your host user, causing permission errors on bind-mounted files. The pragmatic fix for local dev: add user: "${UID}:${GID}" to the WordPress service and export those variables in your shell profile, or use a Makefile target that sets them automatically.

Performance on macOS

Docker Desktop on macOS runs Linux containers in a VM, and bind mount I/O has historically been slow for large WordPress installations. Mitigations include: using Docker’s VirtioFS file sharing (enabled in Docker Desktop > Settings > General), keeping wp-content/uploads in a named volume rather than a bind mount, and enabling synchronized file shares (available in Docker Desktop 4.27+). For most theme and plugin development, modern VirtioFS is fast enough.

Not Production-Ready Without Hardening

A local dev compose file is not a production deployment. Before promoting to any public environment, harden the stack: remove MailHog, enforce TLS termination, set resource limits (mem_limit, cpus), use read-only container filesystems where possible, pull images from a private registry with pinned digests, and run containers as non-root. View our services for containerized WordPress infrastructure built for production scale.

Dev-to-Staging-to-Production Parity

The real payoff of a Docker WordPress workflow is environment parity. Use the same base images in all three environments; let environment variables and secrets drive the differences. A typical pipeline looks like this:

  • Dev: docker compose up locally; MailHog catches email; Xdebug enabled
  • Staging: Same images deployed via CI (GitHub Actions / GitLab CI) to a container host; real SMTP; Xdebug off; staging domain
  • Production: Same images promoted after staging sign-off; secrets from vault; CDN in front of Nginx; Redis persistence enabled

Because the PHP version, extensions, and web server config are baked into the image, the code that passes staging tests runs on the same binary stack in production. That is the promise Docker keeps.

Setup Checklist

  • Docker Desktop (or Docker Engine + Compose plugin) installed and running
  • docker-compose.yml committed to the repository root
  • .env.example committed; .env in .gitignore
  • Nginx default.conf configured for PHP-FPM proxying
  • Named volume defined for database persistence
  • ./wp-content bind-mounted and tracked in Git (excluding uploads/)
  • WP-CLI service added for database and plugin management
  • MailHog accessible at http://localhost:8025 for email testing
  • Redis Object Cache plugin installed and activated
  • File permissions verified — no 777 directories
  • VirtioFS or synchronized file shares enabled (macOS only)
  • Production hardening checklist completed before any public deployment

Ready to Scale Beyond Local

A solid Docker WordPress development environment eliminates the most common friction points in WordPress engineering: environment drift, slow onboarding, and the dreaded works-on-my-machine bug. It also creates a natural on-ramp to container-native staging and production infrastructure.

If your team is ready to take containerized WordPress beyond development — into auto-scaling, blue-green deployments, or AI-augmented workflows — contact us to discuss how Vilee LLC engineers build and operate WordPress infrastructure at scale.

Frequently Asked Questions

Can I use Docker for WordPress development on Windows?

Yes. Install Docker Desktop for Windows with the WSL 2 backend enabled. Store your project files inside the WSL 2 filesystem (e.g., ~/projects) rather than on the Windows C: drive to get near-native I/O performance. The docker-compose.yml syntax and commands are identical across Windows, macOS, and Linux.

How do I import an existing WordPress database into the Docker container?

Place your SQL dump (e.g., dump.sql) in the project root, then run: docker compose exec -T db mariadb -u”$DB_USER” -p”$DB_PASSWORD” “$DB_NAME” < dump.sql. After import, run a WP-CLI search-replace to swap the production domain for your local URL: docker compose run --rm wpcli search-replace 'https://production.com' 'http://localhost:8080' --all-tables.

Is this Docker WordPress setup suitable for production use?

The compose file in this guide is designed for development. Production deployments require additional hardening: TLS termination, removal of development-only services like MailHog, read-only container filesystems, non-root users, resource limits, image digest pinning, and secrets management. Vilee LLC operates containerized WordPress at scale with all of these controls in place — reach out via our contact page to learn more.

Talk to us →