Docker Compose: WordPress + MySQL Complete Reference

Type: Software Reference Confidence: 0.94 Sources: 7 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

Service Configuration

ServiceImagePortsVolumesKey Env
WordPresswordpress:6.7-php8.3-apache8080:80wordpress_data:/var/www/htmlWORDPRESS_DB_HOST=db
MySQL 8.0mysql:8.0Internal onlydb_data:/var/lib/mysqlMYSQL_ROOT_PASSWORD, MYSQL_DATABASE
MySQL 8.4 LTSmysql:8.4Internal onlydb_data:/var/lib/mysqlMYSQL_ROOT_PASSWORD, MYSQL_DATABASE
MariaDB 10.11mariadb:10.11Internal onlydb_data:/var/lib/mysqlMARIADB_ROOT_PASSWORD, MARIADB_DATABASE
MariaDB 11mariadb:11Internal onlydb_data:/var/lib/mysqlMARIADB_ROOT_PASSWORD, MARIADB_DATABASE
phpMyAdminphpmyadmin:58081:80NonePMA_HOST=db
WP-CLIwordpress:cli-php8.3Nonewordpress_data:/var/www/htmlSame DB env as WordPress

WordPress Environment Variables

VariableRequiredDefaultPurpose
WORDPRESS_DB_HOSTYes--Database hostname (use service name)
WORDPRESS_DB_USERYes--Database username
WORDPRESS_DB_PASSWORDYes--Database password
WORDPRESS_DB_NAMEYes--Database name (must exist)
WORDPRESS_TABLE_PREFIXNowp_Table prefix (change for security)
WORDPRESS_DEBUGNo""Set to 1 to enable WP_DEBUG
WORDPRESS_CONFIG_EXTRANo""Additional wp-config.php PHP code
WORDPRESS_*_FILENo--Load any var from a file (Docker secrets)

MySQL Environment Variables

VariableRequiredDefaultPurpose
MYSQL_ROOT_PASSWORDYes*--Root user password
MYSQL_DATABASENo--Database to create on first run
MYSQL_USERNo--Non-root user to create
MYSQL_PASSWORDNo--Password for MYSQL_USER
MYSQL_RANDOM_ROOT_PASSWORDNo*--Generate random root password
MYSQL_*_FILENo--Load any var from a file (Docker secrets)

Decision Tree

START: What kind of WordPress Docker setup do you need?
├── Development environment (local machine)?
│   ├── YES → Use basic docker-compose.yml with phpMyAdmin + port 8080
│   │   ├── Need WP-CLI? → Add wordpress:cli service sharing same volumes
│   │   └── Need custom plugins? → Use custom Dockerfile extending wordpress image
│   └── NO ↓
├── Production deployment?
│   ├── YES → Use production compose with:
│   │   ├── .env file for secrets (never commit to git)
│   │   ├── Named volumes for data persistence
│   │   ├── restart: unless-stopped on all services
│   │   ├── No phpMyAdmin exposed publicly
│   │   ├── Reverse proxy (Nginx/Traefik) with SSL
│   │   └── Health checks on all services
│   └── NO ↓
├── Need MariaDB instead of MySQL?
│   ├── YES → Replace mysql image with mariadb, use MARIADB_* env vars
│   └── NO ↓
├── Need multisite WordPress?
│   ├── YES → Add WORDPRESS_CONFIG_EXTRA with WP_ALLOW_MULTISITE
│   └── NO ↓
└── DEFAULT → Start with the basic development compose file

Step-by-Step Guide

1. Create project directory and environment file

Set up the project structure with a .env file to keep secrets out of docker-compose.yml. [src4]

mkdir wordpress-docker && cd wordpress-docker

cat > .env << 'EOF'
MYSQL_ROOT_PASSWORD=change_me_root_2026
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=change_me_wp_2026
WORDPRESS_TABLE_PREFIX=wp_
EOF

echo ".env" >> .gitignore

Verify: cat .env shows your environment variables.

2. Create docker-compose.yml

Define WordPress, MySQL, and phpMyAdmin services with named volumes and health checks. [src1] [src2]

services:
  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  wordpress:
    image: wordpress:6.7-php8.3-apache
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
    volumes:
      - wordpress_data:/var/www/html

  phpmyadmin:
    image: phpmyadmin:5
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8081:80"
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}

volumes:
  db_data:
  wordpress_data:

Verify: docker compose config resolves without errors.

3. Start the stack

Launch all services and verify health. [src3]

docker compose up -d
docker compose ps
docker compose logs -f wordpress

Verify: curl -s -o /dev/null -w '%{http_code}' http://localhost:8080 returns 302 or 200.

4. Complete WordPress installation via WP-CLI

Automate the installation wizard with WP-CLI. [src1]

docker compose run --rm \
  -e WORDPRESS_DB_HOST=db \
  -e WORDPRESS_DB_USER=${MYSQL_USER} \
  -e WORDPRESS_DB_PASSWORD=${MYSQL_PASSWORD} \
  -e WORDPRESS_DB_NAME=${MYSQL_DATABASE} \
  wordpress:cli-php8.3 \
  wp core install \
    --url="http://localhost:8080" \
    --title="My WordPress Site" \
    --admin_user=admin \
    --admin_password=change_me_admin \
    [email protected]

Verify: Visit http://localhost:8080 and see your site title.

5. Back up your data

Export database and wp-content files. [src5]

# Database backup
docker compose exec db \
  mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}" \
  > backup-$(date +%Y%m%d).sql

# WordPress files backup
docker compose exec wordpress \
  tar czf /tmp/wp-content-backup.tar.gz -C /var/www/html/wp-content .
docker compose cp wordpress:/tmp/wp-content-backup.tar.gz ./

Verify: head -5 backup-*.sql shows MySQL dump header.

6. Restore from backup

Restore database and files from backup. [src5]

# Restore database
docker compose exec -T db \
  mysql -u root -p"${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}" \
  < backup-20260227.sql

# Restore wp-content
docker compose cp ./wp-content-backup.tar.gz wordpress:/tmp/
docker compose exec wordpress \
  tar xzf /tmp/wp-content-backup.tar.gz -C /var/www/html/wp-content

Verify: Check row count in posts table to confirm data restored.

Code Examples

Docker Compose: Production with Docker secrets

# docker-compose.prod.yml -- Production WordPress
services:
  db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - db_data:/var/lib/mysql
    secrets:
      - db_root_password
      - db_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 30s
      timeout: 10s
      retries: 5

  wordpress:
    image: wordpress:6.7-php8.3-apache
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    expose:
      - "80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress_data:/var/www/html
    secrets:
      - db_password

volumes:
  db_data:
  wordpress_data:

secrets:
  db_root_password:
    file: ./secrets/db_root_password.txt
  db_password:
    file: ./secrets/db_password.txt

Custom Dockerfile: WordPress with plugins and PHP tuning

FROM wordpress:6.7-php8.3-apache

# Install WP-CLI
RUN curl -o /usr/local/bin/wp \
    https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
    && chmod +x /usr/local/bin/wp

# PHP upload limits
RUN echo "upload_max_filesize = 64M" \
    > /usr/local/etc/php/conf.d/uploads.ini \
    && echo "post_max_size = 64M" \
    >> /usr/local/etc/php/conf.d/uploads.ini \
    && echo "memory_limit = 256M" \
    >> /usr/local/etc/php/conf.d/uploads.ini

# Copy custom themes and plugins
COPY ./themes/ /usr/src/wordpress/wp-content/themes/
COPY ./plugins/ /usr/src/wordpress/wp-content/plugins/

Docker Compose: WordPress with WP-CLI service

services:
  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:6.7-php8.3-apache
    restart: unless-stopped
    depends_on:
      - db
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
    volumes:
      - wordpress_data:/var/www/html

  wpcli:
    image: wordpress:cli-php8.3
    depends_on:
      - db
      - wordpress
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
    volumes:
      - wordpress_data:/var/www/html
    entrypoint: wp
    command: "--info"
    user: "33:33"

volumes:
  db_data:
  wordpress_data:

Bash: WP-CLI management commands

# Install and activate a plugin
docker compose run --rm wpcli plugin install woocommerce --activate

# Update all plugins
docker compose run --rm wpcli plugin update --all

# Search-replace URLs (domain migration)
docker compose run --rm wpcli search-replace \
  'http://localhost:8080' 'https://example.com' --all-tables

# Export/import database
docker compose run --rm wpcli db export /var/www/html/backup.sql
docker compose run --rm wpcli db import /var/www/html/backup.sql

# Check status
docker compose run --rm wpcli core version
docker compose run --rm wpcli plugin list

Anti-Patterns

Wrong: Hardcoded passwords in docker-compose.yml

# BAD -- passwords visible in version control
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: my_secret_password
      MYSQL_PASSWORD: wordpress123

Correct: Environment variables from .env file

# GOOD -- passwords loaded from .env (excluded from git)
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
# .env file contains actual values, listed in .gitignore

Wrong: No volume persistence

# BAD -- all data lost when container is removed
services:
  db:
    image: mysql:8.0
    # No volumes -- data ephemeral
  wordpress:
    image: wordpress:latest
    # No volumes -- uploads, plugins lost

Correct: Named volumes for all persistent data

# GOOD -- data persists across container recreations
services:
  db:
    image: mysql:8.0
    volumes:
      - db_data:/var/lib/mysql
  wordpress:
    image: wordpress:latest
    volumes:
      - wordpress_data:/var/www/html

volumes:
  db_data:
  wordpress_data:

Wrong: Using 'localhost' as database host

# BAD -- WordPress tries to connect to itself
services:
  wordpress:
    environment:
      WORDPRESS_DB_HOST: localhost

Correct: Using Docker service name

# GOOD -- Docker DNS resolves 'db' to MySQL container
services:
  wordpress:
    environment:
      WORDPRESS_DB_HOST: db

Wrong: Exposing MySQL to host in production

# BAD -- MySQL accessible from outside Docker
services:
  db:
    ports:
      - "3306:3306"

Correct: Keep MySQL internal only

# GOOD -- MySQL only reachable by other containers
services:
  db:
    expose:
      - "3306"

Common Pitfalls

Diagnostic Commands

# Check running services and health status
docker compose ps

# View logs
docker compose logs wordpress
docker compose logs db

# Test database connectivity from WordPress
docker compose exec wordpress \
  php -r "new PDO('mysql:host=db;dbname=wordpress', 'wordpress', getenv('WORDPRESS_DB_PASSWORD'));"

# Check MySQL is accepting connections
docker compose exec db mysqladmin ping -h localhost

# Inspect PHP configuration
docker compose exec wordpress php -i | grep -E 'upload_max|post_max|memory_limit'

# Check disk usage of volumes
docker system df -v | grep -A5 "VOLUME NAME"

# Verify file permissions
docker compose exec wordpress ls -la /var/www/html/wp-content/

# Container resource usage
docker stats --no-stream

Version History & Compatibility

ComponentVersionStatusNotes
WordPress6.7CurrentPHP 8.1-8.3 supported, 8.3 recommended
WordPress6.6SupportedLast version supporting PHP 8.0
MySQL8.4 LTSCurrent LTSSupported through 2032, recommended
MySQL8.0EOL April 2026Migrate to 8.4 LTS before EOL
MariaDB10.11 LTSCurrent LTSSupported through Feb 2028
Docker ComposeV2CurrentBuilt into Docker CLI
Docker ComposeV1EOL (June 2023)Do not use
PHP8.3CurrentDefault in wordpress:latest
phpMyAdmin5.xCurrentSupports MySQL 8.x and MariaDB

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Need a quick local WordPress development environmentRunning 10+ high-traffic WordPress sitesKubernetes with horizontal pod autoscaling
Self-hosting WordPress on a single server or VPSNeed a managed WordPress experienceManaged hosting (WP Engine, Kinsta)
Need to test plugins/themes in isolationNeed auto-scaling for traffic spikesCloud-managed containers (AWS ECS, Cloud Run)
Building a reproducible WordPress deploymentNeed a static/JAMstack site, not a CMSHugo, Next.js, or Astro

Important Caveats

Related Units