Local WordPress Development with Docker

Published in WordPress.

Happy new year! I hope you enjoyed the holidays (assuming you didn’t have to work) and are now back, eager to learn. I sure am and I’m back with a fantastic tutorial for you: how to use Docker for WordPress development. I actually announced it a few days ago via Twitter (in Catalan):

Let me start with a brief story: I started my journey as a WordPress developer using LAMP (Linux + Apache + MySQL + PHP). Unfortunately, I quickly realized that, when working on more than one project at the same time, the setup was a mess, as they all used the same server, the same database… I then moved to Vagrant (VVV in particular), an environment designed for WordPress development, but it was a pain in the ass: my 6-year old computer had a lot of trouble running multiple Virtual Box instances and everything was extremely slow. I then tried to move to Local by Flywheel, because Antonio seemed to enjoy the environment a lot… but it never arrived to Linux! Wasn’t there a fast, reliable, easy-to-use option for me?

In the end, I decided I had to learn more about Docker and see if this tool I’d heard of would fit my needs. If you want to learn more about Docker too and want to use it in your WordPress developments, don’t miss this guide – learn how to build development environments quickly and efficiently.

Docker

Docker is a computer program that performs operating-system-level virtualization, also known as “containerization”. This has two main advantages compared to the classic LAMP setup:

  • containers are isolated from each other and the communication between them is limited, which increases security
  • tools, libraries, config files, and everything the container needs to run is in the container itself, which means we’ll have a clean and tidy setup – once we’re done with the container, we can remove it and everything will be gone.

In my opinion, the main advantage Docker has over other virtualization softwares is the virtualization environment it uses by default: runC. This environment runs all containers on the host OS (instead of running a full virtual machine), which makes things way faster and more lightweight. That means the host (our PC) and the guest (the container) will share a lot of resources and everything will run like a charm in an “old” computer.

Docker Installation

To install Docker on Linux (Debian/Ubuntu), just run the following command:

Follow the instructions on screen and wait until apt is done.

Once Docker is installed, we can use it with the docker command. The only problem with Docker is the fact that you’ll need to run it with admin privileges using sudo. But we can solve this. If you’d rather run docker without sudo (and thus avoid typing your password often), add your user to the docker group:

where your-user is obviously your username in Linux. BTW, you might need to log out and log back in to actually apply this change.

How to Install Docker on Mac or Windows

If you’re using Mac or Windows, you can also install Docker. Just follow the instructions on Docker’s website:

How to Use Docker

So, now that we have Docker set up and ready to be used, it’s time we give it a shot. As I said, Docker is a tool for managing contained apps. If we want to launch a container, we need to run the following command:

For example, try out this command:

to launch the classic Hello World within a Docker container. If everything worked as expected, you’ll first see something like this in your terminal:

This gives us some interesting information. First, Docker looked for an image named hello-world, but it couldn’t find it. Second, it looked for that image in an online image repository, and this time it succeeded and was able to download it. And that’s one of the great things about Docker: it has an image repository with several packed apps we can use as Docker containers (similar to what we have in WordPress, with the plugin and theme directories).

Now, back to our terminal, we can see the actual output of the Hello World container:

And that’s it! You’ve already run your first Docker container 😍

If you now type the following command:

you’ll see the container Docker created using the hello-world image:

To remove it, just type:

making sure that you type in the appropriate CONTAINER ID.

And that’s basically all there is to Docker… So let’s jump into yet another tool (Docker Compose) and learn how to use it as WordPress developers 😉

Docker Compose

As you can imagine, in Docker’s image repository there’s a WordPress image available. You might think that we simply need to pull that image and we’d be able to start a new WordPress site, but that’s actually not true:

  • On the one hand, this image doesn’t include the database system. So, if we want to start our WordPress site, we’ll need to install MySQL somewhere (or use a MySQL container).
  • On the other hand, the WordPress image requires some configuration parameters (such as, for instance, where’s the database and the credentials to access it). So everytime we run the instance, we need to specify those arguments.

Using the docker command alone can be complicated, specially when we have to start multiple containers all at once and some depend on others (as WordPress does). Luckily, there’s a tool called docker-compose that uses a configuration file to describe all the containers we need, the dependencies they have with each other, and their specific setup.

Let’s start by creating a new directory where Docker Compose’s config file will reside. For example, create ~/docker/test and add a file named docker-compose.yml with the following content:

I know this file might seem complicated, but it isn’t. Look carefully: the file simply defines the two services (or containers, if you will) that we need to run WordPress: mysql and wordpress. The former (mysql) contains information about the image we need (which is obviously a MySQL image, version 5.7) and some config parameters. The most relevant are probably the port mapping (we’ll be able to access the database using port 8081 in our host, which maps to 3306 in the guest) and the database setup itself (user, password, and so on).

The latter is WordPress itself (wordpress) and it follows a similar approach. Here we tell Docker Compose that we want WordPress to be accessible via the port 8080 in our host. We also specify that WordPress relies on the other service: mysql. And we finally add some additional configuration parameters (essentially, those to access the database).

Once we’re done, save and exit the file, and type the following command:

and Docker will download the WordPress and MySQL images and will start our WordPress.

Just wait for a couple of minutes and, once it’s done, go to http://localhost:8080 with your web browser (notice how the port we’re using here matches the one we specified in the configuration file) and you’ll see your new WordPress site:

Installing WordPress on a Docker Container
Installing WordPress on a Docker Container.

From this moment on, if we want to create new WordPress instances, you’ll simply have to repeat the steps we’ve discussed. That is, you’ll create a new directory in ~/docker/, you’ll add the docker-compose.yml file, and you’ll set it up. Just make sure that you’re using different ports for this new instance, or you won’t be able to run them both at the same time (for example, use 8082 for WordPress and 8083 for MySQL).

Once you’re done, you can stop the container running the following command:

Just make sure you use stop instead of down:

as down will not only stop the container, but also remove it completely. That is, if you restart a container using docker-compose up after you shut it down using the down command, your WordPress will start from scratch (an empty database, an uninstalled WordPress, etc).

Docker and WordPress Development

Great! You almost have all you need to develop WordPress plugins and themes using Docker effectively. I hope my explanations so far have been helpful and you now better understand what Docker is and how it works.

In my opinion, there are only two things missing to be 100% effective with this setup:

  • How to add your plugin or theme in the Docker container.
  • How to use domain names like http://content.local instead of http://localhost:port with Docker.

Adding Your Project into a Docker Container

Every time I have to work on a new plugin or theme, I create a new Docker container. If you follow the guide so far, you’ve probably realized that the WordPress installation created by Docker is completely empty and doesn’t contain any of our plugins or themes. So, assuming I’m working on a new plugin, how do I add that plugin in the wp-content/plugins/ folder that lies in the container?

There are multiple options to expose directories from the container and make them available in our host machine. The one I like the most maps the directory of our project (and only that directory) to a directory in WordPress. Let’s see this with a concrete example.

Assume I’m working on my plugin with my laptop. This plugin is currently in ~/dev/nelio-content/. I obviously want this plugin to be included in my development environment, so that I can test it and see that all the changes I apply work as expected. The first thing I’ll do is add a docker-compose.yml file in my plugin. The content of said file will be an exact copy of what we’ve already seen, but with one extra option:

Essentially, we simply added a new directive named volumes and we specify the mapping we want. That is, we want the current directory (namely, our plugin’s directory, identified with a dot .) mapped to /var/www/html/wp-content/plugins/nelio-content (which is a directory in the WordPress container). Easy, right? After all, the syntax we’re using is the same we used for port mapping, but we’re applying it to directories now.

And that’s it! If we now start our container, we’ll see that our plugin is available in the Dashboard » Plugins screen.

Using Domain Names

I personally don’t like accessing development installations using localhost and a port. I think using domain names like http://content.local is way easier. So, how can we achieve that? Well, we need a few things…

First, we need to tell our computer that a certain domain name (for instance, content.local) is the computer itself. This is as easy as editing the /etc/hosts file and mapping this new domain name to the localhost IP:

Great! Right now http://content.local is exactly equivalent to http://localhost. But that’s not exactly what we want… What we want is to map http://content.local to http://localhost:8080. To do that, we need a proxy server that maps requests to the former address to the latter. This is also very easy to achieve. Just create a new folder named ~/docker/proxy with the following docker-compose.yml file:

Then, run docker network create proxy and start your new container using docker-compose up -d. This will create a network that all our WordPress projects will use (more on that in a minute) and install the proxy that’ll do all the magic.

Finally, we simply need to modify our WordPress container so that it automatically notifies the proxy about its existence:

Okay, so we’ve done a few things here. Let’s take a closer look at all the changes we applied:

  • In the WordPress environment section we added two new attributes: VIRTUAL_HOST and VIRTUAL_PORT. These are the attributes that our proxy will use to map the VIRTUAL_HOST to localhost:VIRTUAL_PORT.
  • Since we want our WordPress service to talk to our proxy, we need to make sure both containers are in the same network. This is pretty easy: we simply add a networks option in WordPress and include the frontend network (you can use whatever keyword you want). Then, at the end of the file, I added a few more rules. In particular, I added a networks section and specified that there’s an external network identified by the keyword frontend with the name proxy. Notice this external network is the same one we used in the proxy’s docker-compose.yml.
  • Finally, I want to make sure that WordPress and MySQL can talk to each other too, so I have to make sure they’re also in a common network. To do that, I added them both in another network named backend, which is internal and, therefore, nobody else can access.

And that’s it! You now know everything I know about this tool. I hope you enjoyed this guide and, if you did, please share it with your colleagues and let me know in the comment section below!

Featured Image by Abigail Lynn.

PoorNot badGoodGreatExcellent (5 votes, average: 3.00 out of 5)
Loading...

14 thoughts on “Local WordPress Development with Docker

  1. Thank you very much David, you helped me a lot!

    Best regards!

  2. thanks for this great tutorial but I can’t access to content.local, please record a video for this tutorial

    1. Hey Akbar! Glad you liked the tutorial. We might record a video in the future; in the meantime, please make sure you followed all the instructions AND edited /etc/hosts to map content.local to 127.0.0.1.

  3. Thanks for the tutorial. I set up a second project using the same .yml file and now when I try navigating to wp-admin for either site i get “Error establishing a database connection” indicating the credentials in the wp-config are incorrect. If i navigate to localhost:8080 I get ERR_EMPTY_RESPONSE. I’ve tried taking down the instance, removing the containers and images and spinning them back up, even uninstalled and reinstalled docker with no success. Any suggestions?

    1. Hi Eli!

      If you want to run more than one Docker instance at the same time, keep in mind you have to tweak their yml files so that each instance uses their own ports (WordPress’ and MySQL’s, of course). Otherwise, they’ll collide and you’ll have trouble.

      Thus, one instance would be WordPress-8080:80 and MySQL-8081:3306 and the other one would be WordPress-8082:80 and MySQL-8083:3306.

      I hope this helps! Let me know if it worked or if you’re still stuck.

  4. Thank you for such in-depth, step-by-step instructions! Everything works when I access the site through localhost:8080, but, I am having trouble making a unique domain name. After some issues, restarted computer just to start afresh. When I run docker-compose up -d for the proxy yml file, I get:

    ERROR: Network proxy declared as external, but could not be found. Please create the network manually using docker network create proxy and try again.

    So, I run docker network create proxy and it generates a huge string. Cool. I’m assuming this means the proxy is running? Then I do docker-compose up -d for the main file which contains the wordpress and sql containers. Running docker network ls -a shows me proxy with its own network id, but there is also a “MyFolderName”_backend (I think in your example it would be test_backend) also running with its own network id.

    I have updated the etc/hosts file to point 127.0.0.1 to wordpress.test. Running wordpress.test in chrome gives me a “This site can’t be reached. Try checking connection/proxy/firewall ERR_CONNECTION_REFUSED”.

    BUT, going to localhost:8080 works. So, what am I missing? If it helps, I’m on Ubuntu 18.04 LTS. Trying to learn many things at once 🙂 I’m usually on a mac.

    Your help would be very much appreciated!

    1. Hi Maria!

      You’re absolutely right, I forgot to mention that you first need to create the shared network (which, in my post, was named “proxy”). After running docker network create proxy you still need to (a) run docker-compose up -d on the container that will act as a proxy and (b) then run docker-compose up -d in your WordPress project.

      The behavior is actually pretty simple: when you start a docker container, everything is isolated in their own private network. In fact, if you were to list all the networks docker created (using docker network list) you’d see it created a bunch of them: one for every docker machine you started. However, we want to make sure that our WordPress site can use a “beautiful” domain name, so we need a tool (a proxy) that can redirect requests from the “beautiful” domain name to the actual WordPress container. This tool is another Docker container: jwilder/nginx-proxy. Now, since we want this proxy to be able to talk to all our WordPress sites, we simply tell Docker that our proxy uses a certain external network (conveniently named proxy) and so does our WordPress installation.

      So, to sum up:

      1. Create the proxy (you’ll only have to run this one)
      2. Start the proxy container
      3. Start your WordPress container

      This should do the trick.

      1. Hello David! Thank you for you most thorough of answers. Seriously, whenever I go out looking for tutorials or try to troubleshoot errors, I read them thinking, “This is for someone with much more knowledge than I have. They’re assuming I know too much!” lol I’m new to all of this, so it’s refreshing to have so much guidance. I am having another issue… it starts at “BUT” below.

        I was getting into another error, which I fixed, but thought I’d mention in case someone else is having issues. So, when I ran “docker-compose up -d” on the proxy yml file I would get this:

        ERROR: for proxy_nginx-proxy_1 Cannot start service nginx-proxy: driver failed programming external connectivity on endpoint proxy_nginx-proxy_1 (#some-long-id-num): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use

        ERROR: for nginx-proxy Cannot start service nginx-proxy: driver failed programming external connectivity on endpoint proxy_nginx-proxy_1 (#another-long-id-num): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use
        ERROR: Encountered errors while bringing up the project.

        So, I thought this meant that 3306 is already being used and went ahead and changed it to 3307. In terminal, everything looks good and running!

        BUT, I’m getting “Error establishing a database connection” when I go to the unique url, which I see was also mentioned in another comment. The console in the window gives “Failed to load resource: the server responded with a status of 500 (Internal Server Error)”. I don’t have any other containers running, have double checked all the port numbers in the yml files, and the etc/hosts file is updated. Thoughts? What might have I missed?

        … I swear…. if it’s a semi colon or an extra space somewhere…..

        1. I’m glad you like our tutorials! We try to keep “all” audiences in mind when writing them.

          Regarding your issue, I guess it’s probably because you started your docker machines before everything was properly set up and know they’re having trouble finding each other. For example, as I mentioned in my previous response, you have to create an external network that the proxy and the WordPress service in your WordPress docker machine will share. You had already figured that out: you had to run docker network create to create it. The thing is, once the network is created, starting a docker machines will pull the network ID (that “long string” you mentioned) and use that… so it’s possible that your docker machines are not connected with each other.

          So here’s what I would do: destroy all your docker machines. First, run docker-compose down. Then, run a docker ps -a and remove all the machines using their IDs (docker remove ID1 ID2 ... IDn). Next list your networks: docker network list. You should only have the proxy network and the default one: none. Remove any other networks.

          Once you have a clean setup, start your proxy using docker-compose up -d. Finally, open your WordPress setup, make sure the ports are properly set (that is, map some random ports in your host, such as 8080 and 8081, to the guest ports, i.e., 80 and 3036). Basically, use the setup I shared in my post. And start this machine too: docker-compose up -d.

          And it should work!

          If it doesn’t, I’ll see if I can prepare a script that gets the environment up and running 😉

  5. Hola David. Estoy intentando entrar en el mundo de Docker y tengo unas dudas

    No se trata de código fuente ni configuraciones, o “como hacerlo”, solo dudas del tipo ¿se puede hacer … con Docker?

    1- Se pueden tener varias versiones de Php corriendo al mismo tiempo? Esto porque en mi maquina localhost tengo la versión mas nueva de Php y en la maquina de pruebas del trabajo tengo otra versión mas antigua. Aparte en producción tengo otra versión digamos intermedia en número de versión

    2- Se pueden tener varios proyectos con solo crear las carpetas? Por ejemplo var/www/html/proyecto1 y var/www/html/proyecto2 e indicarle con cual versión de Php corran

    3- Pienso instalarlo en un Amazon EC2 Linux.. se puede conectar con algún GUI en Windows para hacer las configuraciones/carpetas de manera mas sencilla?

    4- Hasta ahora no he encontrado algún tutorial de como mover ese Docker con todos sus proyectos/configuraciones a otro servidor

    Saludos desde Sinaloa, México

    1. ¡Hola! Muchas gracias por contactar con nosotros. Aunque no soy un experto en Docker (yo mismo estoy aprendiendo a usarlo y esta entrada es parte de mi aprendizaje), intentaré responderte.

      1. En principio sí. De la misma forma que puedes tener varias versiones de PHP dentro de un Linux, también puedes tenerlo en el Linux que hay corriendo dentro del Docker. Por otro lado, nada impide tener múltiples contenedores Docker corriendo, cada uno con setups diferentes (y, por lo tanto, con versiones de PHP diferentes).

      2. Sí, si cada carpeta corre en su propio contenedor Docker.

      3. No lo sé; supongo que no.

      4. Yo tampoco, pero no lo necesito 😉

      Un saludo,
      David

  6. Hello, David: I really liked your tutorial, I was especially baffled, on how to create multiple instances of WordPress. You have cleared up my confusion, thanks.

    I do have a question about PHPMyAdmin, how would I add PHPMyAdmin to manage multiple databases? Is it necessary to have PHPMyAdmin?

    1. You obviously need a tool to manage your database. If you look at the docker-composer.yml file we created, you’ll see that we exposed the database in localhost:8081, which means you can connect to the database using your preferred application.

Leave a Reply

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

I have read and agree to the Nelio Software Privacy Policy

Your personal data will be located on SiteGround and will be treated by Nelio Software with the sole purpose of publishing this comment here. The legitimation is carried out through your express consent. Contact us to access, rectify, limit, or delete your data.