Docker is useful for WordPress in two scenarios: local development (where it shines) and production deployment (where it adds complexity but pays off for teams). Here is a practical setup for each.

The basic Docker setup for WordPress local development

Create a docker-compose.yml in your project:

services:
  wordpress:
    image: wordpress:php8.2-apache
    ports:
      - "8080:80"
    volumes:
      - ./wp-content:/var/www/html/wp-content
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_NAME: wp_local
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: strong_password_here
    depends_on:
      mysql:
        condition: service_healthy

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: wp_local
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: strong_password_here
      MYSQL_ROOT_PASSWORD: root_password_here
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  mysql_data:

Create uploads.ini for upload limits:

file_uploads = On
upload_max_filesize = 256M
post_max_size = 256M
memory_limit = 512M

Start it:

docker compose up -d
docker compose logs -f wordpress

Visit http://localhost:8080 to run the WordPress installer.

Why mount wp-content as a volume

The key line is:

- ./wp-content:/var/www/html/wp-content

Without this, plugins and themes installed in the container disappear on the next docker compose up. Mounting the local directory means:

  • Plugins installed via the WordPress admin survive restarts
  • You can use your local editor and Git
  • The container stays stateless

Using WP-CLI in a Docker container

WP-CLI is pre-installed in the official WordPress Docker image. Run it with:

docker compose exec wordpress wp plugin list
docker compose exec wordpress wp theme list
docker compose exec wordpress wp db export backup.sql

If you need to run a WP-CLI command that takes a long time (plugin updates, database imports), increase the PHP execution time in uploads.ini or pass --allow-root if WP-CLI requires it.

Database access from the host

For Sequel Ace, TablePlus, or MySQL Workbench:

# Get the container's internal IP
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' your_project_mysql_1

Or add a port mapping to the compose file:

mysql:
  image: mysql:8.0
  ports:
    - "3307:3306"
  # ...

Then connect to localhost:3307 from your host database client.

Production Docker: the differences

Development Docker and production Docker are different problems. In production:

  • You should not use the official WordPress image directly
  • You need a custom Dockerfile to install your specific plugins and theme during the image build
  • Database should not be in a container (use a managed database or at minimum an external MySQL instance)
  • You need health checks, restart policies, and log management

A minimal production Dockerfile:

FROM wordpress:php8.2-apache

# Install custom plugins and theme at build time
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN composer require wpackagist-plugin/wordfence wpackagist-plugin/wp-super-cache

# Production configuration
RUN a2enmod ssl headers rewrite

docker-compose for staging

A staging setup with Nginx, PHP-FPM, and Redis:

services:
  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./wp-content:/var/www/html/wp-content
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php

  php:
    image: wordpress:php8.2-fpm-alpine
    volumes:
      - ./wp-content:/var/www/html/wp-content
      - ./php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
    environment:
      WORDPRESS_DB_HOST: ${DB_HOST}
      WORDPRESS_DB_NAME: ${DB_NAME}
      WORDPRESS_DB_USER: ${DB_USER}
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
    depends_on:
      - redis

  redis:
    image: redis:alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

Common Docker + WordPress problems

Permissions errors

The Apache process runs as www-data. If your local files are owned by your user, the container cannot write to them.

Fix:

# Change ownership to match the container
sudo chown -R 33:33 ./wp-content

Or run with a named user that matches your UID:

user: "${UID}:33"

Slow performance on macOS

Docker on macOS uses a virtualised Linux kernel, making file I/O significantly slower than on Linux. For large WordPress sites, this matters.

Fixes:

  • Use Docker Desktop with VirtioFS (faster than the older gRPC-FUSE backend)
  • Move database and cache inside containers (not mounted volumes)
  • Use docker compose up without -d during active development if performance is critical

Container IP changes after restart

Use service names (like mysql, redis) as hostnames — Docker’s internal DNS resolves these to the correct container IPs, regardless of IP changes.

Database container filling up

The MySQL container’s data lives in a Docker volume. Monitor it:

docker volume inspect your_project_mysql_data
du -sh /var/lib/docker/volumes/your_project_mysql_data/_data

Periodically clean up old images and stopped containers:

docker system prune -f