Skip to main content
WP HealthKit

Docker Compose for WordPress: Local Dev Environment

July 22, 202616 min readTutorialsBy Jamie

Developing WordPress plugins in local environments that accurately mirror production is challenging. Developers might run WordPress on their Mac with native PHP and MySQL, but production runs in containerized environments with specific PHP versions, extensions, and database configurations. These mismatches lead to bugs discovered only in production. Docker Compose provides a solution: developers define multi-container environments mirroring production, including WordPress with specific PHP versions, MariaDB databases, Redis caching, Xdebug debugging, and Mailhog email testing. This comprehensive guide walks through building a complete WordPress development environment that enables plugin testing against the same configuration deployed to production.

Table of Contents

  1. Docker Compose Fundamentals
  2. WordPress Container Configuration
  3. Database and Cache Services
  4. Xdebug Integration for Debugging
  5. Email Testing with Mailhog
  6. Volume Mounts for Plugin Development
  7. Network and Service Communication
  8. Production Parity and CI Integration

Docker Compose Fundamentals

Docker Compose is a tool for defining and running multi-container Docker applications. Instead of manually creating and linking containers, a single docker-compose.yml file describes all services, their configurations, and relationships. Running docker-compose up starts all services ready for development.

Benefits of Docker Compose for WordPress development include: first, reproducibility—the exact same environment runs on every developer's machine and in CI/CD pipelines; second, isolation—services run in containers without affecting host system; third, simplicity—complex infrastructure is managed by a simple configuration file; fourth, documentation—the compose file serves as infrastructure documentation; finally, cleanup—stopping the environment cleans up all containers and volumes, preventing system pollution.

The basic workflow is: define services in docker-compose.yml, run docker-compose up to start everything, develop WordPress plugins, run tests, and docker-compose down when finished. This cycle repeats constantly.

WordPress Container Configuration

The foundation of the development environment is a WordPress container running a specific PHP version with necessary extensions. Rather than using WordPress' official image (which has minimal extensions), create a custom image extending the official image with development tools.

# Dockerfile for WordPress development
FROM wordpress:6.4-php8.2-apache

# Install development tools and PHP extensions
RUN apt-get update && apt-get install -y \
    git \
    wget \
    curl \
    vim \
    zip \
    unzip \
    libmcrypt-dev \
    libicu-dev \
    && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install \
    mysqli \
    pdo \
    pdo_mysql \
    intl \
    gd \
    bcmath \
    zip

# Install Xdebug for debugging
RUN pecl install xdebug && docker-php-ext-enable xdebug

# Install Composer for dependency management
RUN curl -sS https://getcomposer.org/installer | php -- \
    --install-dir=/usr/local/bin --filename=composer

# Configure Apache and PHP
RUN a2enmod rewrite

# Set working directory
WORKDIR /var/www/html

The Compose file defines how the WordPress service runs:

# docker-compose.yml
version: '3.8'

services:
  wordpress:
    build: .  # Use custom Dockerfile above
    image: wordpress-dev:latest
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DEBUG: 'true'
      WORDPRESS_DEBUG_LOG: /var/www/html/wp-content/debug.log
      WP_MEMORY_LIMIT: 256M
    volumes:
      - wordpress_data:/var/www/html
      - ./plugins:/var/www/html/wp-content/plugins
      - ./themes:/var/www/html/wp-content/themes
      - ./wp-config.php:/var/www/html/wp-config.php
    depends_on:
      - mysql
    networks:
      - wordpress-network

  mysql:
    image: mariadb:10.6
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - wordpress-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10

volumes:
  wordpress_data:
  mysql_data:

networks:
  wordpress-network:

Running the environment:

# Start all services
docker-compose up -d

# Check status
docker-compose ps

# Access WordPress
# Visit http://localhost:8080 in browser

# Stop services
docker-compose down

# Stop and remove data
docker-compose down -v

Database and Cache Services

WordPress development requires database persistence and optional caching for realistic testing. The configuration includes MariaDB database service and optional Redis caching layer.

MariaDB configuration in the compose file specifies image, credentials, persistence, and health checks. Health checks ensure WordPress doesn't attempt connections before the database is ready:

services:
  mysql:
    image: mariadb:10.6
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql-init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - wordpress-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10
    ports:
      - "3306:3306"  # Expose for debugging with GUI tools

Redis caching layer improves performance and allows testing cache invalidation:

  redis:
    image: redis:7-alpine
    networks:
      - wordpress-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      timeout: 10s
      retries: 5
    ports:
      - "6379:6379"

  wordpress:
    # ... other config
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      # ... other environment variables
      WP_CACHE: 'true'
      WP_REDIS_HOST: redis
      WP_REDIS_PORT: 6379

Install WP Redis plugin to integrate WordPress with Redis:

# Inside WordPress container
docker-compose exec wordpress wp plugin install redis-cache --activate
docker-compose exec wordpress wp redis enable

Xdebug Integration for Debugging

Xdebug enables step-by-step debugging of PHP code. Configure Xdebug to communicate with your IDE (VS Code, PhpStorm, etc.) for interactive debugging.

Xdebug configuration in the WordPress Dockerfile:

RUN pecl install xdebug && docker-php-ext-enable xdebug

# Configure Xdebug
RUN echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.client_port=9003" >> /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.log=/var/www/html/xdebug.log" >> /usr/local/etc/php/conf.d/xdebug.ini

VS Code configuration (.vscode/launch.json):

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for XDebug",
      "type": "php",
      "port": 9003,
      "pathMapping": {
        "/var/www/html": "${workspaceFolder}"
      }
    }
  ]
}

PhpStorm configuration:

  • Go to Settings > Languages & Frameworks > PHP > Debug
  • Set Debug port to 9003
  • Enable "Force break at first line when no path mapping specified"
  • Go to Settings > Languages & Frameworks > PHP > Debug > DBGp Proxy
  • Configure IDE key as needed

With Xdebug configured, set breakpoints in your IDE and step through plugin code:

// In your plugin code, Xdebug allows stepping through
class SecureFilter {
    public function sanitizeInput($input) {
        // Set breakpoint here
        $cleaned = sanitize_text_field($input);
        return $cleaned;
    }
}

Email Testing with Mailhog

WordPress plugins send emails (notifications, password resets, etc.). In development, prevent sending to real addresses by using Mailhog, which intercepts all email and provides a web interface to view sent messages.

Add Mailhog to docker-compose.yml:

  mailhog:
    image: mailhog/mailhog:latest
    ports:
      - "1025:1025"  # SMTP port
      - "8025:8025"  # Web interface
    networks:
      - wordpress-network

  wordpress:
    # ... other config
    environment:
      # ... other environment variables
      # Configure WordPress to use Mailhog for all emails
      SMTP_HOST: mailhog
      SMTP_PORT: 1025

Install and configure WP Mail SMTP plugin to send through Mailhog:

docker-compose exec wordpress wp plugin install wp-mail-smtp --activate

Access Mailhog web interface at http://localhost:8025 to view all sent emails. This enables testing email-related functionality without sending to real addresses.

Volume Mounts for Plugin Development

Volume mounts link directories from your host machine to the container, enabling live code editing with immediate effect in the running WordPress instance.

Effective volume configuration mounts plugin directories, theme directories, and wp-config.php:

volumes:
  - wordpress_data:/var/www/html
  - ./plugins:/var/www/html/wp-content/plugins
  - ./themes:/var/www/html/wp-content/themes
  - ./uploads:/var/www/html/wp-content/uploads
  - ./wp-config-local.php:/var/www/html/wp-config.php
  - ./php.ini:/usr/local/etc/php/conf.d/custom.php
  - ./apache.conf:/etc/apache2/sites-enabled/000-default.conf

This allows developing plugins locally:

# Create plugin directory structure
mkdir -p plugins/my-security-plugin/src
mkdir -p plugins/my-security-plugin/tests

# Develop plugin files locally
# Files are immediately available in container
# Changes to plugin code take effect immediately

Persistent data volumes allow data survival across container restarts:

volumes:
  wordpress_data:
    driver: local
  mysql_data:
    driver: local

services:
  wordpress:
    volumes:
      - wordpress_data:/var/www/html

Network and Service Communication

Docker Compose creates a network enabling inter-service communication using service names as hostnames. WordPress container communicates with MySQL using mysql as the hostname, not localhost.

Service discovery is automatic. When WordPress needs to connect to the database, it uses the hostname defined in depends_on:

wordpress:
  depends_on:
    mysql:
      condition: service_healthy
  environment:
    WORDPRESS_DB_HOST: mysql  # Service name used as hostname

Custom network configuration:

networks:
  wordpress-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Services connect to the network, automatically discovering each other:

services:
  wordpress:
    networks:
      - wordpress-network
  mysql:
    networks:
      - wordpress-network
  redis:
    networks:
      - wordpress-network

Production Parity and CI Integration

Development environments should mirror production as closely as possible. Docker Compose makes this feasible by using the same images and configurations.

Production parity checklist:

  • Same PHP version (e.g., 8.2)
  • Same database (MariaDB vs MySQL)
  • Same PHP extensions enabled
  • Same environment variables
  • Same resource constraints

Use the same compose file or extend it for development-specific additions:

# docker-compose.yml (shared between dev and prod)
version: '3.8'
services:
  wordpress:
    image: wordpress:6.4-php8.2-apache
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}

# docker-compose.override.yml (development-only, loaded automatically)
version: '3.8'
services:
  wordpress:
    build: .  # Build custom image for dev
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DEBUG: 'true'
      XDEBUG_MODE: debug

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

CI integration uses the same compose file to test plugins:

# .github/workflows/test.yml
name: Plugin Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Start WordPress
        run: docker-compose up -d
      
      - name: Wait for services
        run: docker-compose exec -T wordpress bash -c 'until wp db check; do sleep 1; done'
      
      - name: Install test suite
        run: docker-compose exec -T wordpress wp plugin install phpunit-for-wordpress --activate
      
      - name: Run plugin tests
        run: docker-compose exec -T wordpress wp phpunit
      
      - name: Run static analysis
        run: docker-compose exec -T wordpress vendor/bin/phpstan analyse src/
      
      - name: Stop containers
        run: docker-compose down

Advanced Development Patterns

Beyond basic setup, Docker Compose supports advanced patterns enabling sophisticated development workflows. Multi-container plugin development allows testing plugin interactions and cross-plugin compatibility. Run multiple WordPress containers with different configurations to test plugin behavior across versions, PHP versions, and theme combinations.

# docker-compose.yml with multiple WordPress versions
version: '3.8'

services:
  wordpress-latest:
    build:
      context: .
      args:
        WP_VERSION: latest
        PHP_VERSION: 8.2
    image: wordpress-dev:latest
    ports:
      - "8080:80"
    depends_on:
      mysql:
        condition: service_healthy

  wordpress-legacy:
    build:
      context: .
      args:
        WP_VERSION: 6.1
        PHP_VERSION: 8.1
    image: wordpress-dev:6.1
    ports:
      - "8081:80"
    depends_on:
      mysql:
        condition: service_healthy

  mysql:
    image: mariadb:10.6
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10

WP HealthKit plugin developers benefit from testing across versions. The same plugin must work with WordPress 6.0+, PHP 7.4+, and various theme combinations. Docker Compose environments enable comprehensive compatibility testing automatically.

Performance profiling and optimization is facilitated through monitoring services. Add services for monitoring resource usage, profiling slow queries, and analyzing PHP performance:

# Add performance monitoring
services:
  adminer:
    image: adminer
    ports:
      - "8888:8080"
    depends_on:
      - mysql

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

  # Optional: Prometheus for metrics
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

Developers can then use these tools during development: Adminer for database inspection, Mailhog for email testing, and Prometheus for performance metrics collection.

Environment Management and Workflows

Effective Docker Compose development requires managing multiple environments and workflows. Development environments typically use loose security settings and debug logging. Staging environments more closely mirror production. Production environments have minimal debug output and strict security policies.

Managing environment-specific configurations prevents hard-coding values. Use .env files with environment variables that docker-compose interpolates:

# .env file for development
DB_PASSWORD=devpassword
WP_DEBUG=true
WORDPRESS_DEBUG_LOG=true
CACHE_ENABLED=true
PHP_MEMORY_LIMIT=256M
WP_ENVIRONMENT_TYPE=development

The docker-compose.yml references these variables:

services:
  wordpress:
    environment:
      WORDPRESS_DEBUG: ${WP_DEBUG}
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WP_MEMORY_LIMIT: ${PHP_MEMORY_LIMIT}

Different environments use different .env files:

# Development
cp .env.development .env
docker-compose up

# Testing
cp .env.testing .env
docker-compose -p test up

# Staging
cp .env.staging .env
docker-compose -p staging up

This pattern enables safe configuration management without exposing secrets in version control. Developers each use their own .env files with local overrides.

Backup and restore workflows are critical for development. Docker volumes containing database changes must be backed up before resetting to clean state:

# Backup current database state
docker-compose exec mysql mysqldump -uwordpress -pwordpress wordpress > backup.sql

# Reset to clean state
docker-compose down -v
docker-compose up -d

# Restore from backup
docker-compose exec -T mysql mysql -uwordpress -pwordpress wordpress < backup.sql

Automating these backups prevents data loss:

#!/bin/bash
# backup.sh - automate database backups

BACKUP_DIR="./backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

docker-compose exec -T mysql mysqldump \
  -uwordpress -pwordpress wordpress > \
  $BACKUP_DIR/backup_${TIMESTAMP}.sql

# Keep only last 10 backups
cd $BACKUP_DIR
ls -t | tail -n +11 | xargs rm -f

Additional Resources

Broader Context and Best Practices

Code quality in WordPress plugins extends far beyond aesthetic preferences or stylistic choices. Quality code is fundamentally about maintainability, which directly impacts security, performance, and reliability over time. When code is well-structured with clear separation of concerns, consistent naming conventions, and comprehensive error handling, bugs are easier to spot, fixes are faster to implement, and new features can be added without introducing regressions.

The WordPress plugin ecosystem benefits enormously from shared coding standards and conventions. When developers follow established patterns for hook usage, option storage, database operations, and API interactions, their code becomes instantly readable to other WordPress developers. This readability matters not just for open-source contributions but also for commercial plugins where team members change over time.

Technical debt in WordPress plugins accumulates silently until it becomes a crisis. Each shortcut taken during development, each deprecated function left in place, each test not written adds to the debt balance. Unlike financial debt, technical debt compounds unpredictably. Proactive quality management through automated code analysis identifies these time bombs before they detonate.

Modern WordPress development demands a level of engineering discipline that matches the platform's maturity. Plugins that started as simple utility scripts a decade ago now handle payment processing, personal data management, and business-critical workflows. Applying professional software engineering practices like automated testing, continuous integration, dependency management, and architectural patterns isn't over-engineering for WordPress.

Broader Industry Context and Best Practices

Effective WordPress development tutorials balance conceptual understanding with practical implementation. Rather than simply providing code to copy, well-crafted tutorials explain the reasoning behind architectural decisions, helping developers adapt patterns to their specific requirements. This approach builds lasting knowledge rather than creating dependency on tutorial authors. WP HealthKit serves as a practical learning tool, providing real-time feedback on code quality that reinforces tutorial concepts. When following along with tutorials, developers should experiment with variations to deepen their understanding, testing edge cases and intentionally introducing errors to observe how systems respond.

Development environment setup significantly impacts learning effectiveness and productivity. Modern WordPress development workflows leverage Docker for consistent environments, WP-CLI for automated setup, and version control for tracking changes. Hot reloading and debugging tools provide immediate feedback that accelerates the development cycle. WP HealthKit integrates into development workflows to provide continuous quality feedback as code evolves. Tutorials should encourage developers to invest time in proper tooling setup early, as the productivity gains compound significantly over time, making future learning and development substantially more efficient.

WordPress plugin architecture decisions made early in development have lasting consequences that are expensive to change later. Choosing between class-based and functional approaches, deciding on data storage strategies, and designing hook integration points all shape the plugin long-term maintainability. WP HealthKit helps developers evaluate these architectural decisions against established best practices, catching potential issues before they become deeply embedded. Studying well-architected open source plugins provides practical examples of effective patterns, while contributing to existing projects offers mentored learning opportunities that accelerate professional development.

Testing and deployment practices separate professional WordPress development from hobbyist approaches. Automated testing catches regressions before they reach users, while staged deployment pipelines enable safe rollouts with easy rollback capability. WP HealthKit validates that plugins include appropriate test coverage and follow deployment best practices. Continuous integration services can run WP HealthKit audits automatically on every commit, ensuring quality standards are maintained throughout the development lifecycle. Developers who establish good testing and deployment habits early find that these practices actually accelerate development by reducing time spent debugging and fixing production issues.

Strategic Considerations and Implementation Patterns

Advanced WordPress development techniques build upon fundamental concepts to address complex real-world requirements. Custom database tables, background processing, webhook integration, and multi-site aware development represent skills that distinguish professional plugin developers. Understanding WordPress internals deeply enough to extend or modify core behavior safely requires studying source code and contributing to the community. WP HealthKit serves as a learning companion that provides feedback on advanced implementations, helping developers identify when their approaches deviate from established patterns or introduce subtle issues that may not be immediately apparent during development.

Frequently Asked Questions

How do I persist database changes between container restarts?

Named volumes in docker-compose.yml persist data across restarts. docker-compose up restarts containers with existing data intact. Only docker-compose down -v removes volume data.

Can I run multiple WordPress environments simultaneously?

Yes, create separate directories with different docker-compose.yml files or use compose project names: docker-compose -p project1 up and docker-compose -p project2 up run separate environments simultaneously.

How do I import an existing database dump?

Place the SQL dump in the MySQL init directory, and it runs during container initialization:

cp database-dump.sql ./mysql-init.sql
docker-compose up  # Automatically imports the dump

What's the minimum system resource requirement?

Docker Compose development environments typically need 4GB RAM and 10GB disk space. Larger WordPress installations or test suites require more.

How do I debug database queries?

Enable MySQL general query log:

mysql:
  environment:
    MYSQL_GENERAL_LOG: 'true'
    MYSQL_GENERAL_LOG_FILE: /var/log/mysql/query.log

Then view logs: docker-compose logs mysql

Can I use Docker Compose for production?

Docker Compose is designed for development and testing. For production, use Kubernetes, Docker Swarm, or managed container services (AWS ECS, Google Cloud Run, etc.) that provide redundancy, load balancing, and monitoring.

Conclusion

Docker Compose provides WordPress developers with powerful local development environments that accurately mirror production configurations. By defining all services (WordPress, database, caching, debugging, email) in a single compose file, developers achieve consistency across machines and CI/CD pipelines. WP HealthKit plugins benefit from this setup by testing against production-equivalent environments, reducing bugs and ensuring plugins work reliably in real-world deployments.

The investment in setting up Docker Compose development environments pays dividends through reduced debugging time, faster testing, and confident deployment of WordPress plugins and themes.

Ready to streamline WordPress plugin development with Docker? Upload your WordPress installation to WP HealthKit to test plugins in production-parity environments. Explore WP HealthKit's ecosystem integrations for development tools and CI/CD platforms, and learn more about automated testing in our guide to WordPress plugin GitHub Actions CI/CD. Reference the Docker Compose documentation for additional configuration options and advanced patterns.

Ready to audit your plugin?

WP HealthKit checks for all the issues in this article and 40+ more across 49 verification layers.

Comments

Docker Compose for WordPress: Local Dev Environment | WP HealthKit