server-ovh-prod-01-main/configs/wordpress-docker-migration-...

8.2 KiB

WordPress Docker Migration Guide - Lessons Learned

Last Updated: 2025-09-19 Based on: BestSolarTech.com migration experience

Critical Gotchas & Solutions

1. Variable Escaping in Docker Compose Environment Variables

Problem: $_SERVER variables in WORDPRESS_CONFIG_EXTRA are interpreted as shell variables, causing warnings and broken configuration.

Symptom:

level=warning msg="The \"_SERVER\" variable is not set. Defaulting to a blank string."

Wrong:

WORDPRESS_CONFIG_EXTRA: |
  if (isset(\$_SERVER['HTTP_X_FORWARDED_PROTO'])) {  

Correct:

WORDPRESS_CONFIG_EXTRA: |
  if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO'])) {  

Solution: Use $$ to escape dollar signs in Docker Compose environment variables.

2. Traefik v3 Host Rule Syntax

Problem: Multiple hosts in Traefik labels don't work with comma separation.

Wrong:

- "traefik.http.routers.site.rule=Host(`domain1.com`, `domain2.com`, `domain3.com`)"

Correct:

- "traefik.http.routers.site.rule=Host(`domain1.com`) || Host(`domain2.com`) || Host(`domain3.com`)"

Solution: Use || (OR operator) to separate multiple hosts in Traefik v3.

3. MariaDB Version Compatibility

Problem: MariaDB version mismatches cause redo log format errors.

Error:

[ERROR] InnoDB: Unsupported redo log format. The redo log was created with MariaDB 10.11.14.

Solution:

  • Check the original database version before migration
  • Use the same or newer MariaDB version
  • MariaDB 10.11 is a safe choice for most WordPress sites

4. Real Simple SSL Plugin Behind Traefik

Problem: Real Simple SSL plugin causes redirect loops when WordPress is behind Traefik reverse proxy.

Solution:

  1. Disable the plugin BEFORE migration:
# Via database (serialized array - be careful!)
UPDATE wp_options SET option_value = 'a:0:{}' WHERE option_name = 'active_plugins';
# OR use WP-CLI after migration
docker exec CONTAINER wp --allow-root plugin deactivate really-simple-ssl
  1. Add proxy detection to wp-config.php:
/* Handle SSL behind reverse proxy */
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
    $_SERVER['SERVER_PORT'] = 443;
}
define('FORCE_SSL_ADMIN', true);

5. WordPress URL Management Strategy

Problem: Changing URLs too early causes redirect loops and access issues.

Best Practice:

  1. Start with a temporary domain (e.g., temp.example.com)
  2. Test everything thoroughly
  3. Only update to production domain after confirming everything works
  4. Keep both domains in Traefik configuration during transition

Database URL Update:

UPDATE wp_options SET option_value = 'https://yourdomain.com'
WHERE option_name IN ('siteurl', 'home');

6. Database Password Reset for Existing Databases

Problem: Existing database has unknown root password.

Solution - Use skip-grant-tables:

# Start MariaDB with skip-grant-tables
docker run -d --name temp_db -v ~/services/SERVICE/db_data:/var/lib/mysql mariadb:10.11 --skip-grant-tables

# Reset passwords
docker exec temp_db mysql -e "
USE mysql;
FLUSH PRIVILEGES;
ALTER USER 'root'@'%' IDENTIFIED BY 'new_password';
CREATE USER IF NOT EXISTS 'wordpress'@'%' IDENTIFIED BY 'wp_password';
GRANT ALL PRIVILEGES ON dbname.* TO 'wordpress'@'%';
FLUSH PRIVILEGES;
"

# Stop temp container and start normal service
docker stop temp_db && docker rm temp_db

Complete Migration Checklist

Pre-Migration

  • Document current WordPress version, PHP version, active theme
  • List all active plugins (especially security/SSL plugins)
  • Check database size and MariaDB version
  • Note table prefix (often not default wp_)
  • Create full backup of files and database

File Migration

  • Copy WordPress files preserving structure:
    sudo cp -r /backup/wordpress ~/services/site/wordpress
    sudo chown -R www-data:www-data ~/services/site/wordpress
    
  • Copy database files:
    sudo cp -r /backup/mysql ~/services/site/db_data
    sudo chown -R 999:999 ~/services/site/db_data
    
  • Copy/create uploads.ini for PHP settings

Docker Compose Configuration

  • Use correct MariaDB version (10.11 recommended)
  • Escape $ signs as $$ in environment variables
  • Use || for multiple hosts in Traefik rules
  • Include both internal and traefik networks
  • Set health check for database
  • Configure volumes for persistence

WordPress Configuration

  • Update wp-config.php database host to 'mariadb:3306'
  • Add proxy SSL detection code
  • Remove hardcoded URLs (use $_SERVER['HTTP_HOST'])
  • Disable SSL plugins before starting

Testing Phase

  • Start with temporary domain
  • Test database connectivity
  • Verify no redirect loops
  • Check admin panel access
  • Test file uploads
  • Verify all plugins working

Production Cutover

  • Update DNS records to new server
  • Update database URLs to production domain
  • Test SSL certificate generation
  • Monitor logs for errors
  • Have rollback plan ready

Sample Docker Compose Template

services:
  wordpress:
    image: wordpress:6.7-php8.3-apache
    container_name: ${SITE_NAME}_wp
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: mariadb:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: ${DB_NAME}
      WORDPRESS_TABLE_PREFIX: ${TABLE_PREFIX:-wp_}
      WORDPRESS_CONFIG_EXTRA: |
        /* Handle SSL behind reverse proxy */
        if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
            $$_SERVER['HTTPS'] = 'on';
            $$_SERVER['SERVER_PORT'] = 443;
        }
        define('FORCE_SSL_ADMIN', true);
        define('WP_MEMORY_LIMIT', '256M');        
    volumes:
      - ./wordpress:/var/www/html
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
    networks:
      - internal
      - traefik
    depends_on:
      mariadb:
        condition: service_healthy
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.${SITE_NAME}.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.${SITE_NAME}.entrypoints=web"
      - "traefik.http.routers.${SITE_NAME}.middlewares=redirect-to-https@file"
      - "traefik.http.routers.${SITE_NAME}-secure.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.${SITE_NAME}-secure.entrypoints=websecure"
      - "traefik.http.routers.${SITE_NAME}-secure.tls=true"
      - "traefik.http.routers.${SITE_NAME}-secure.tls.certresolver=letsencrypt"
      - "traefik.http.services.${SITE_NAME}.loadbalancer.server.port=80"

  mariadb:
    image: mariadb:10.11
    container_name: ${SITE_NAME}_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./db_data:/var/lib/mysql
    networks:
      - internal
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 60s

networks:
  traefik:
    external: true
  internal:
    external: true

WP-CLI Installation

Install WP-CLI in WordPress container for management:

docker exec CONTAINER bash -c 'curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp'

# Use with --allow-root flag
docker exec CONTAINER wp --allow-root plugin list
docker exec CONTAINER wp --allow-root cache flush

Quick Troubleshooting

Problem Solution
404 errors from Traefik Restart Traefik: docker restart traefik
Redirect loops Check database URLs match expected domain
SSL certificate errors Check DNS points to server, wait for Let's Encrypt
Database connection failed Verify credentials, check container network
Plugin conflicts Disable via database or WP-CLI

Important Notes

  • Always test on a temporary domain first
  • Keep backups before making changes
  • Document all credentials securely
  • Monitor logs during migration
  • Have a rollback plan ready