# Migration: hair-select.ch from 199 to 208 Migration from nginx-proxy (199.241.137.4) to Traefik (208.87.129.133) **Date:** 2025-12-10 **Domain:** hair-select.ch **Table Prefix:** wp_393247_ ## Source (199.241.137.4) - Containers: `ch_hair_select_wp`, `ch_hair_select_wp_db` - Database: `ch_hair_select_wp_db_name` - WordPress files: `/home/g/Documents/workspace/wordpress_sites/ch_hair_select/wordpress_files` - Setup: nginx-proxy + letsencrypt companion, MySQL 5.7, bind mounts ## Target (208.87.129.133) - Containers: `php-wp-ch_hair_select_wp`, `php-wp-ch_hair_select_wp_db`, etc. - Database: `php-wp-ch_hair_select-db_name` - Setup: Traefik, MariaDB 11, Docker volumes - Deployment: Drone CI from Gitea --- ## Migration Steps ### 1. Project Setup ```bash # Copy template from danipages project cp -r php-wp-com_danipages php-wp-ch_hair_select cd php-wp-ch_hair_select # Clean up rm -rf .git MIGRATION.md # Initialize git git init git add -A git commit -m "Initial commit from template" ``` ### 2. Generate Secure Credentials ```bash # Generate .env.prod with secure passwords and WordPress salts ./generate-env-secrets.sh php-wp-ch_hair_select hair-select.ch wp_393247_ ``` This script: - Generates random passwords using `openssl rand -base64 32` - Fetches WordPress salts from https://api.wordpress.org/secret-key/1.1/salt/ - Updates .env.prod with all required values ### 3. Hydrate Vault ```bash # Run from project directory vault-new-project --env_file .env.prod --name php-wp-ch_hair_select --inventory_hostname sn48 ``` ### 4. Create Gitea Repository 1. Create repo at https://gitea.sn48.zivili.ch: `php-wp-ch_hair_select` 2. Add remote: ```bash git remote add origin ssh://git@gitea.sn48.zivili.ch:222/gbili/php-wp-ch_hair_select.git ``` ### 5. Activate in Drone 1. Go to https://drone.sn48.zivili.ch 2. Find `php-wp-ch_hair_select` repository 3. Click "Activate" to enable builds ### 6. Push to Trigger Deployment ```bash git push -u origin master ``` Wait for Drone to deploy containers on 208. ### 7. Export Data from Source (199) ```bash # Export database (84MB) ssh g@199.241.137.4 "docker exec ch_hair_select_wp_db sh -c 'mysqldump -u root -p\$MYSQL_ROOT_PASSWORD ch_hair_select_wp_db_name' > /tmp/hair_select_db.sql" # Export wp-content via docker (184MB) - avoids permission issues with Wordfence ssh g@199.241.137.4 "docker exec ch_hair_select_wp tar -czf /tmp/wp_content.tar.gz -C /var/www/html wp-content && docker cp ch_hair_select_wp:/tmp/wp_content.tar.gz /tmp/hair_select_wp_content.tar.gz" ``` ### 8. Transfer to Target (208) ```bash scp g@199.241.137.4:/tmp/hair_select_db.sql g@199.241.137.4:/tmp/hair_select_wp_content.tar.gz g@208.87.129.133:/tmp/ ``` ### 9. Import Data on Target (208) ```bash # Copy SQL into container and import ssh g@208.87.129.133 "docker cp /tmp/hair_select_db.sql php-wp-ch_hair_select_wp_db:/tmp/hair_select_db.sql && docker exec php-wp-ch_hair_select_wp_db sh -c 'mariadb -u root -p\"\$MYSQL_ROOT_PASSWORD\" \"\$MYSQL_DATABASE\" < /tmp/hair_select_db.sql'" # Extract wp-content to volume ssh g@208.87.129.133 "docker run --rm -v php-wp-ch_hair_select_wp-data:/data -v /tmp:/backup alpine sh -c 'cd /data && tar -xzf /backup/hair_select_wp_content.tar.gz --strip-components=1'" ``` ### 10. Update DNS Update A record for `hair-select.ch` to point to `208.87.129.133` Verify: ```bash dig hair-select.ch +short # Should return: 208.87.129.133 ``` ### 11. Restart Container for SSL ```bash ssh g@208.87.129.133 "docker restart php-wp-ch_hair_select_wp" ``` Verify SSL certificate: ```bash echo | openssl s_client -connect hair-select.ch:443 -servername hair-select.ch 2>/dev/null | openssl x509 -noout -issuer -subject -dates ``` ### 12. Verify Site ```bash # Test internally (before DNS propagates) ssh g@208.87.129.133 "curl -sL -H 'Host: hair-select.ch' http://localhost | head -50" # Test externally curl -sI https://hair-select.ch ``` ### 13. Decommission Old Containers ```bash ssh g@199.241.137.4 "docker stop ch_hair_select_wp ch_hair_select_wp_db && docker rm ch_hair_select_wp ch_hair_select_wp_db" ``` --- ## Key Differences: 199 vs 208 | Aspect | 199 (Source) | 208 (Target) | |--------|--------------|--------------| | Reverse proxy | nginx-proxy + letsencrypt companion | Traefik | | Database | MySQL 5.7 | MariaDB 11 | | DB command | `mysql` | `mariadb` | | Volume type | Bind mounts (entire /var/www/html) | Docker volumes (wp-content only) | | SSL config | LETSENCRYPT_HOST env var | Traefik labels (certresolver) | | Deployment | Manual docker-compose | Drone CI | --- ## Troubleshooting ### Database import fails with "Access denied" The `-i` flag with `docker exec` doesn't pass stdin properly when piping from outside. Solution: Copy SQL file into container first, then import from inside. ### wp-content export permission denied Wordfence creates files with restricted permissions. Use `docker exec` to tar from inside the container, then `docker cp` to extract. ### SSL shows self-signed cert Deploy AFTER DNS points to new server. If deployed before, restart the container after DNS propagates: ```bash docker restart php-wp-ch_hair_select_wp ``` ### Site redirects to /wp-admin/install.php TABLE_PREFIX mismatch. Check source database tables: ```bash docker exec mariadb -u root -p -e 'SHOW TABLES;' ``` Update TABLE_PREFIX in .env.prod, re-run vault-new-project, redeploy. --- ## Data Sizes - Database dump: 84MB - wp-content archive: 184MB - Divi theme version: 4.27.5 (PHP 8.3 compatible)