· 6 min read · Docker, Ubuntu, WordPress, VPS, DevOps
Moving a WordPress site from shared hosting to a Dockerised Ubuntu VPS
A practical, step-by-step guide to moving WordPress sites and business apps like Odoo ERP from shared hosting to an Ubuntu VPS with Docker, without losing data, email or rankings.
Shared hosting is where most small business websites start, and for a brochure site it's fine. Then the business grows. There's a second site, an ERP, a booking system and an email campaign. One day everything goes offline at once and nobody can say why, because nobody has access to the server underneath.
That's roughly the situation I walked into at Next Generation Solutions in Muscat. Critical outages had taken Odoo ERP and every web service offline, and the legacy WordPress stack had SQL injection vulnerabilities. I moved production onto an Ubuntu VPS configured from scratch, and containerised Odoo ERP and three production websites with Docker. On another project, for Aer Lingus Virtual, I migrated a site from WordPress on cPanel to Hostinger, and performance improved by 80 percent with 50 percent more organic traffic.
This guide is the order I work in when I do a move like that. It's written for developers and for business owners who want to understand what their developer should be doing.
Why move off shared hosting at all?
Shared hosting isn't bad. It's just a trade-off that stops making sense at a certain size:
- You share resources with strangers. Another customer's traffic spike or compromised site can slow down or take down yours.
- You can't see the machine. When something breaks you depend on a support ticket, and you often can't read the logs that would explain it.
- You can't run everything you need. Business applications like Odoo ERP want their own processes, databases and versions, which shared plans rarely allow.
- Everything is tangled together. One outdated plugin or PHP version can hold every site on the account hostage.
A VPS (virtual private server) gives you a machine of your own. Docker then lets you run each site and application in its own isolated container on that machine, so they stop affecting each other.
Step 1: Take a complete inventory
Most failed migrations fail because something was forgotten, not because something was done wrong. Before touching anything, write down everything the old host is doing:
- Every website and web application, and the PHP or runtime version each one needs
- Every database, with its size
- Every DNS record, especially MX, SPF, DKIM and DMARC records for email
- Mailboxes, if email is hosted on the same account
- Scheduled tasks (cron jobs): backups, invoice runs, feed imports, cache clears
- SSL certificates and where they come from
- Anything that sends email: contact forms, WooCommerce, ERP notifications
Email deserves special attention. On shared hosting it often lives on the same account as the website, and changing DNS without planning for it is the fastest way to stop a company receiving mail.
Step 2: Harden the server before anything moves in
Security is far easier on an empty server than on a busy one. My baseline for a fresh Ubuntu VPS looks like this:
# A non-root user for day-to-day work
sudo adduser deploy
sudo usermod -aG sudo deploy
# Firewall: only SSH, HTTP and HTTPS
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
# Security updates install themselves
sudo apt install unattended-upgradesOn top of that: SSH keys instead of passwords, root login over SSH disabled, and a plan for monitoring. You want to hear about a full disk or a stopped container before your customers do.
Step 3: Give every service its own container
The core idea of a Dockerised server is simple: one service per container, and data outside the containers.
- Each website runs in its own container with exactly the PHP version and extensions it needs. Upgrading one site no longer risks the others.
- Business applications get their own containers too. Odoo ERP and its PostgreSQL database are a natural pair.
- Data lives in volumes. Databases, uploads and configuration are stored in Docker volumes or mounted folders, never only inside a container, so rebuilding or updating a container can't wipe them.
- A reverse proxy sits in front. One container receives all web traffic on ports 80 and 443, handles SSL certificates, and passes each request to the right site based on its domain.
A minimal Docker Compose file for one WordPress site shows the pattern:
services:
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_RANDOM_ROOT_PASSWORD: "1"
volumes:
- db_data:/var/lib/mysql
wordpress:
image: wordpress:php8.3-apache
restart: unless-stopped
depends_on: [db]
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
volumes:
- wp_content:/var/www/html/wp-content
volumes:
db_data:
wp_content:Secrets such as database passwords belong in an environment file that isn't committed to version control, not in the Compose file itself.
Step 4: Fix security problems during the move, not after
A migration is the best moment to clean up, because you're already touching every file. For a legacy WordPress install, that means:
- Updating WordPress core, themes and plugins, and deleting anything that isn't used
- Reviewing custom code for database queries built from raw user input, the classic cause of SQL injection, and rewriting them with prepared statements (
$wpdb->prepare()in WordPress) - Resetting admin passwords and removing old user accounts
- Scanning uploads folders for files that shouldn't be there
Moving a compromised site to a new server just moves the compromise. At NexGen, remediating the SQL injection vulnerabilities in the legacy WordPress stack was part of the migration work, not a follow-up ticket.
Step 5: Rehearse the move
Restore a copy of each site onto the new server while the old one is still live. Then test it without touching public DNS by pointing your own computer at the new server with a hosts-file entry. You see the new server; everyone else still sees the old one.
Check the things people actually use:
- Every page template loads, including search and 404 pages
- Forms submit and their emails arrive
- Logins work, including admin
- Scheduled jobs run
- For an ERP: create a test record, print a document, send a notification
Step 6: Cut over with a low TTL
DNS changes take time to spread because resolvers cache records for as long as the record's TTL (time to live) says. A day or two before the switch, lower the TTL on the records you'll change to a few minutes. On the day:
- Put the old site into maintenance mode, or freeze content changes
- Do a final database and file sync to the new server
- Switch the DNS records
- Watch the logs on the new server as traffic arrives
- Once everything is stable, raise the TTL again
Keep the old hosting account running for a week or two as a fallback before cancelling it.
Step 7: Protect your search rankings
A server move on its own doesn't hurt SEO. Changing URLs, breaking pages or going offline does. To keep rankings intact:
- Keep every URL exactly the same, or add permanent (301) redirects for any that change
- Make sure HTTPS works on every domain and subdomain from the first minute
- Check Google Search Console for crawl errors in the days after the move
- Resubmit your sitemap
A faster, more reliable server usually helps rankings over time. The 50 percent organic traffic growth on the Aer Lingus Virtual migration came with an 80 percent performance improvement.
Step 8: Back up, monitor and document
The move isn't finished until someone other than you could run the server:
- Backups of databases and volumes, stored somewhere other than the server itself, with a restore you've actually tested
- Monitoring for uptime, disk space and container health, with alerts that reach a person
- Documentation: what runs where, how to restart it, where secrets are kept, and how to renew certificates
What you get at the end
After the NexGen migration, Odoo ERP and three production websites ran in their own Docker containers on one Ubuntu VPS, with SSL and server monitoring. When something needs attention, it's one container that needs attention, not the whole company's online presence.
If you're running a business in Oman or elsewhere and your sites and systems are still tangled together on shared hosting, get in touch. This is exactly the kind of work I do.