271 lines
8.2 KiB
Markdown
271 lines
8.2 KiB
Markdown
|
|
# 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**:
|
||
|
|
```yaml
|
||
|
|
WORDPRESS_CONFIG_EXTRA: |
|
||
|
|
if (isset(\$_SERVER['HTTP_X_FORWARDED_PROTO'])) {
|
||
|
|
```
|
||
|
|
|
||
|
|
**Correct**:
|
||
|
|
```yaml
|
||
|
|
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**:
|
||
|
|
```yaml
|
||
|
|
- "traefik.http.routers.site.rule=Host(`domain1.com`, `domain2.com`, `domain3.com`)"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Correct**:
|
||
|
|
```yaml
|
||
|
|
- "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:
|
||
|
|
```bash
|
||
|
|
# 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
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Add proxy detection to wp-config.php:
|
||
|
|
```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**:
|
||
|
|
```sql
|
||
|
|
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:
|
||
|
|
```bash
|
||
|
|
# 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:
|
||
|
|
```bash
|
||
|
|
sudo cp -r /backup/wordpress ~/services/site/wordpress
|
||
|
|
sudo chown -R www-data:www-data ~/services/site/wordpress
|
||
|
|
```
|
||
|
|
- [ ] Copy database files:
|
||
|
|
```bash
|
||
|
|
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
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
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:
|
||
|
|
```bash
|
||
|
|
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
|