Migrating WordPress to Docker

From Bare-Metal to Docker
Migrating a legacy WordPress site into Docker isn’t just about moving files; it’s about cleaning up “server rot” and modernizing the stack. Here is the proven workflow for a successful transition.
1. The Extraction (The “Rescue” Phase)
Before touching the new server, extract the essentials from the old host:
- The Database: Use
mysqldump --no-tablespacesto avoid permission errors on modern MariaDB instances. - The Content: You only need the
/wp-contentfolder. Let the official WordPress Docker image provide a fresh, clean core for the rest. - The Credentials: Capture
DB_NAME,DB_USER,DB_PASSWORD, and specifically the$table_prefixfrom the oldwp-config.php.
2. Local Prototyping
Always build the stack on a local machine (like a Fedora laptop) first.
- Docker Compose: Use a multi-container setup (WordPress + MariaDB).
- Auto-Import: Place your
.sqldump into./docker-entrypoint-initdb.d/. MariaDB will automatically “rehydrate” the database on the first boot. - Networking: Change
DB_HOSTinwp-config.phpfromlocalhostto the name of your database service (e.g.,db).
services:
# --- REVERSE PROXY - for use in testing environment ---
# uncoment next lines for local testing on NPM (nginx proxy manager)
# proxy:
# image: "jc21/nginx-proxy-manager:latest"
# restart: always
# ports:
# - "8484:80"
# - "8443:443"
# - "881:81" # Admin Web UI
# volumes:
# - ./proxy/data:/data
# - ./proxy/letsencrypt:/etc/letsencrypt
# networks:
# - wp_network
# --- SHARED DATABASE ---
db:
image: "mariadb:10.11"
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${PANSMAN_DB_NAME}
MYSQL_USER: ${PANSMAN_DB_USER}
MYSQL_PASSWORD: ${PANSMAN_DB_PASSWORD}
volumes:
- ./mariadb_data:/var/lib/mysql
# Auto-import your old databases on first run, for testing purposes only
# - ./pansman/init.sql:/docker-entrypoint-initdb.d/pansman.sql:ro
# - ./manosman/init.sql:/docker-entrypoint-initdb.d/manosman.sql:ro
networks:
- wp_network
# --- SITE 1: pan.sman.cloud
pansman:
image: wordpress:latest
restart: always
ports:
- "8081:80" # Map host 8081 to container 80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: ${PANSMAN_DB_NAME}
WORDPRESS_DB_USER: ${PANSMAN_DB_USER}
WORDPRESS_DB_PASSWORD: ${PANSMAN_DB_PASSWORD}
volumes:
- ./pan_sman_cloud:/var/www/html
networks:
- wp_network
# --- SITE 2: mano ` .sman.cloud
manosman:
image: wordpress:latest
restart: always
ports:
- "8082:80" # Map host 8082 to container 80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: ${MANOSMAN_DB_NAME}
WORDPRESS_DB_USER: ${MANOSMAN_DB_USER}
WORDPRESS_DB_PASSWORD: ${MANOSMAN_DB_PASSWORD}
volumes:
- ./mano_sman_cloud:/var/www/html
networks:
- wp_network
networks:
wp_network:
driver: bridge
volumes:
db_data:
3. The Production Deployment
When moving to a server that already hosts other containers or Node.js APIs:
- Reverse Proxy: If port 80/443 is taken by a host-level Nginx, map your WordPress containers to unique local ports (e.g.,
8081,8082). - Host Routing: Use the host’s Nginx to
proxy_passtraffic to the Docker containers. - SSL: Use Certbot on the host machine to handle Let’s Encrypt certificates across all domains.
4. Solving the Permission “Gotcha”
Docker containers run WordPress as the www-data user (UID 33). If your uploaded files are owned by root, WordPress won’t be able to update plugins or upload images.
- The Fix: Run
sudo chown -R 33:33 ./your_site_folderon the host. This ensures the container has full write access immediately.
5. Post-Migration Cleanup
Once live, use WP-CLI (built into the container) to perform a search-and-replace for URLs:
docker compose exec -u www-data <service-name> wp search-replace "http://old-site.test" "https://new-site.cloud" --all-tables
The Technical Stack
| Component | Technology |
|---|---|
| Orchestration | Docker Compose |
| Database | MariaDB 10.11 |
| PHP/App | WordPress (Official Image) |
| Gateway | Nginx (Host-level) |
| SSL | Certbot (Let’s Encrypt) |
Final Verdict
Moving to Docker makes your old site portable. If your server fails tomorrow, you can simply move your project folder to a new VPS, run docker compose up -d, and be back online in minutes.