Move existing WordPress site into Docker

I’ve been running two WordPress blogs for some time and my biggest regret is that they are not running in Docker containers. If I did the right thing in the beginning, I wouldn’t have to worry about whether or not the server upgrade will be safe, or will I be able to recall server configuration when time to migrate comes. I actually would be able to spin up local blog replica, run some experiments on it (new settings, features or design change) and decide whether or not I want move that change into ‘production’.

However, it’s never too late. I’m reluctant to make a big change on the real server without prior tests, so today I’ll try to create local Docker replica of one of my blogs and see how that goes.

Step 0: The idea

The overall process should be relatively simple, as WordPress already has its own Docker image. What’s more, they have a working example of docker-compose.yml file that spins up WordPress container along with mysql, so we have a nice starting point:

I think attaching my blog’s backup (content + mysql data) to containers would actually get most of the job done. There might be some issues with WordPress expecting true domain name while running on localhost, but we might be able to do something about it. So let’s begin.

Step 1: Backup the blog

What I like about WordPress is how easy it is to take its backup. Archive www folder, zip database’s dump, and it’s done. Surprisingly, this is also the thing that I don’t like about WordPress, as I’d rather prefer having clear separation of application from its configuration and data. It looks like there’s some sort of separation, but I’m not sure I can trust it.

So, let’s tar WordPress’s web folder and make mysqldump for codeblog.dotsandbrackets.com:

After these files ended up on local machine, we can move to the step two.

Step 2: Restore the website in a container

I checked WordPress’s Dockerfile and it seems that it will update wp-config.php file with environmental variables passed to container. Net effect is that we won’t have to restore mysql database with the same credentials as we used ‘in production’ and use new ones instead. We even can use root user (default choice for WP Dockerfile).

Very same Dockerfile says that it stores Wordpress’s content in /var/www/html (which is a volume, btw), so I can simply mount my backed up content to that path.

Finally, I’m not comfortable putting any credentials into files that most likely will end up in source control, so I’ll put them into environmental variables instead, e.g.:

Now, untar web content, create initial version of docker-compose.yml file with empty database for now and try to start it:

WordPress container: install

It did start, but we can’t tell if that install page is taken from ‘our’ content or from one that came with container. We need some data. Let’s shut down the web site and go to find some:

Step 3: Restore the database

Googling into mysql’s Dockerfile reveals wonderful picture: whatever we mount to /docker-entrypoint-initdb.d/ path inside of mysql container will be executed when container starts for the first time. It even understands archives! So theoretically, mounting mysql dump to that path should do the trick. Let’s try that. mysql service definition changes just a little bit:

Repeat startup process:

codeblog.dotsandbrackets.com in Docker

Cool! However, there’s one problem. If I try to login, it’ll redirect me to codeblog.dotsandbrackets.com. It’s clearly using hardcoded domain name and we need to fix that.

Step 4. Fix web site address

Quick googling into a problem immediately shows that the following sql queries might help:

mysql’s docker-entrypoint-initdb.d path can accept many files (in alphabetical order), so if I replace oldurl with codeblog.dotsandbrackets.com and www.newurl with 127.0.0.1, put the result into migrate.sql and mount it with - ./migrate.sql:/docker-entrypoint-initdb.d/migrate.sql, the problem should be solved. Let’s give it a try.

Migrate file:

New volumes component in mysql service:

Now, restart the web site:

This time it can navigate to login dialog and even login! But Jetpack plugin looks sad. I think for now I’ll just shut it down.

codeblog.dotsandbrackets.com: logged into container version

However, there’s bigger problem. Clicking on any article causes 404:

Links are broken

I vaguely remember configuring something in blog’s nginx properties when I installed it. Probably it’s time to head back to blog’s host and check out what was that.

Step 5. Fixing the links

OK, the problem’s just gotten a little bit bigger. Configuration line that made the links to work was try_files nginx directive:

But WordPress image that I used comes with Apache, not nginx. Apache does have similar configuration option, but I’m not that familiar with Apache to begin with, so let’s find another way.

WordPress image also comes with fpm configuration, which allows me to attach custom web server to it. I also found rault/nginx-wordpress image that sits and waits to be connected to wordpress:fpm image. It actually might help.

Again, before making any changes to YAML file, stop running containers first:

Then, add new service definition to docker-compose.yml:

Finally, start containers and keep all possible fingers crossed, hoping that it will work:

JetPack plugin is complaining on being in a sandbox again, but after disabling it one more time everything seems to be working now.

codeblog.dotsandbrackets.com in Docker containers

I clicked through the most obvious links, it all seems to be OK. In Docker and OK!

Conclusion

Moving existing WordPress into Docker containers is doable. Most of the process actually was quite straightforward and it’s only web server configuration step (probably specific to my site only) that puzzled me for a moment. I’ll spend some time looking for missing bits of configuration (e.g. host names), testing and measuring performance. But once I’m happy with all of that, I’m definitely moving that into ‘production’.

27 thoughts on “Move existing WordPress site into Docker

  1. Great article, really useful for me having Docker experience already but using this as a neat shortcut to getting a WordPress install ‘dockerised’ for local development. Many thanks!

  2. Hi Pav. I just screwed up by signing up as a volunteer webmaster for a WordPress site. I never used WordPress before but it seems pretty accessible. I sign in on the site however and i see red notifications everywhere. Nothing has been updated for an eternity, which, i’m guessing leaves a bunch of vulnerabilities wide open.

    So i’d like to update wordpress and the different plugins that the site uses. But i’m also a little bit afraid that I’ll mess up something in the process.

    I took a back up of the site by ssh’ing onto the site and creating a sqldump and copying everything from /www on to my local machine.

    But I still don’t really trust myself or my backup. So I’d like to verify, that I have an intact backup and was thinking maybe I could spin everything up in a Docker container.

    I tried spinning it up using your docker-compose settings. But each time i spin up the docker container with the data I still see the install page which I’m guessing means that i’m doing something wrong. Also I’m getting ‘MySQL Connection Error: (2002) Connection refused’. I tried searching for a solution but I couldn’t find anything to get further.

    – Hoping you’ll help me get a little further 🙂

    1. Hi Emma,
      I can’t tell what’s wrong in it without seeing it myself, but as the first step I’d check if mysql hostname, login and password in wp-config.php file are correct. Maybe there was a typo or something in docker-compose settings, so either mysql or wordpress container didn’t receive the credentials.

  3. Hey Pav, this article was just brilliant to figure out a smooth workflow to containerize my client’s website. Really nice and concise article, easy to follow. Hats off and many thanks — you saved me a lot of time here!

  4. This was very helpful to me today. It looks though like you’re not mapping a MySQL volume for /var/lib/mysql in the container, so if you ever did docker-compose down, you’d lose all your changes since you spun up the containers, right?

    1. Oh shi… Yup, you’re right. Missed that one. If I won’t forget this by morning, will update the post with missing binding.
      But I think after docker-compose down the volume still will be there. With random name, but there. I remember I had to explicitly docker volume prune to clean things up. So if that’s the case there’s still a chance to recover that data – docker volume ls to list the volumes and docker volume inspect to find one whose mounting paths look mysql’y. Then you either could add missing volume mapping to docker-compose.yml, docker-compose up+down and move data from old volume to one that docker-compose just created, or even docker volume create with proper name and move data into it before very first compose up. I pro-o-obably tried the first one.

  5. Thank you so much for this!

    As a Docker newbie, this introduction really helped me understand a lot about how docker works.

    Encountered no major issues!

  6. Great write-up. Thanks!

    Just wanted to point out that there’s another way to accomplish “Step 4. Fix web site address”

    The WP_HOME and WP_SITEURL constants can be defined in wp-config.php or wherever.

  7. Hey pav, thanks for this writeup, it has been really helpful 🙂

    Small remark:

    $ mysqldump –add-drop-table -u **** -p **** | gzip > codeblog.dotsandbrackets.com.date +%Y%m%d-%h%M%S.sql.gz
    # Enter password:
    # produces codeblog.dotsandbrackets.com.2070808-024302.sql.gz

    Here you do not specify the name of the table wordpress is using. Running the command like this produces a gzipped man page of mysqldump. Could you please add a placeholder database name here?

  8. hi,

    Hope someone response. I try to do this, really good article, but I stuck on restore files. Firs, as I understand, author just get all wp files, archive it to tar.gz, but it isn’t use this archive in docker-compose.yml. And, I really don’t get why this archiwes is created and what I should to use there in volument part. What is ./codeblog.dotsandbrackets.com in
    volumes:
    – ./codeblog.dotsandbrackets.com:/var/www/html

    Just folder with all wp files?

    1. hey, yes, in the end it’s just a folder. I tarballed WordPress folder when taking a backup, and didn’t mention later in the post that it was untarred and stored next to docker-compose.yml, so it could be mounted to a container later. The only reason for archiving is performance – it’s one thing to download a tarball from a remote host, and the other – a folder with tons of small files.

  9. Thank you! Awesome, always wondered how to do that with an old WP deploy. What about SSL 😜? Maybe try to use the https://caddyserver.com container? It takes care of this automatically, and has one line support for fpm? If someone manages to do this please post. If I do first, will share too.

    1. I actually ended up glueing findings from the post together into Vagrant file and deploying WP instances directly on cloud VMs. The reasoning was that I have several WPs, I want to keep them isolated on cheap hosts, and having Docker around them would be an overkill. From the top of my head I’d probably solve SSL by having a good old nginx as a reverse proxy with SSL in front of my docker host, but again – it’s an overkill for cheap hosts.

  10. Hi. This is an excellent write-up. Do you know if it’s possible to work with different php versions using Docker? For example, if I wanted to test if my site will work with php 5.6, 7.4 and 8, how can I do that?

    1. You can. In the examples above, I was using the latest version of php/fpm at that time – image: wordpress:fpm. However, there are more specific tags, e.g. wordpress:5.7-php8.0-fpm-alpine. Just find the image tag with the version you’re looking for. Or check the sources of those images and make your own build – at the end of the day those images are backed with Dockerfiles with PHP being explicitly installed, so you can change them to whatever you want.

Leave a Reply

Your email address will not be published. Required fields are marked *