In this post we’ll take a step-by-step look at how to start developing WordPress plugins and themes in Docker. Complete code is provided at the end.

What We’ll Cover:

  • Adminer
  • MySql
  • volumes and persistent data
  • plugins
  • themes

Why Docker?

I’ll just list a couple reasons. You can easily share your environment with teams. All they have to do is download the project and run $ docker-compose up. Swapping out different components is easy. If you need Mysql:5.7 in one project and 4.8? in another, it’s super easy!
You can have multiple versions running side-by-side. One client still using PHP 5, while another is on PHP 7? No problem!

Docker is completely isolated from the rest of your system. If you break something, your computer isn’t affected. You won’t have any weird dependency problems. You don’t need to install a bunch of things on your system just for x project. Instead, everything can be neatly packaged within the docker containers so your system can be clean. There’s more, but this isn’t a “benefits of docker” post.


You should already have docker-compose and docker installed on your machine and make sure they’re working and up to date, oh and docker should be running, obviously!!

Knowledge of basic terminal commands. I assume you can figure out how to change directories and understand what I’m doing without explaining it to you.

Step 1. setup MySQL and adminer in Docker

We’ll use docker-compose right off the bat. To use WordPress, you MUST have a database setup. The traditional database for WordPress is MySql so let’s use that. Also, Adminer is much easier to use than phpmyadmin, so I recommend Adminer over phpmyadmin.

Okay! create a project directory for your WordPress project and change into that directory.

mkdir wptut
cd wptut

We need a file called ‘docker-compose.yml’ with the following inside:


version: '3.1'

    image: adminer
      - "8080:8080"

    image: mysql
      MYSQL_ROOT_PASSWORD: password #default USER is root

Our Entire project looks like this:

  - docker-compose.yml

version: ‘3.1’ tells us which version of docker-compose we’re using.

services: is a list of “services” that will be used in the project. Each service will be its own container. All of these containers will be placed in a network so they can communicate with each other.

adminer: and db: are the ‘service names’. They are sort of like aliases that you can use to talk to that container instead of having to use a Hostname or IP address.

image: adminer and image: mysql These lines go and find the latest adminer and mysql image. If they are not available on your machine, they get pulled down from the docker hub. These are used to create containers for adminer and mysql.

ports: – “8080:8080” This maps the port 8080 on your machine to port 8080 on the adminer container. This allows you to visit localhost:8080 to work in adminer.

environment: MYSQL_ROOT_PASSWORD: password sets an environment variable. This allows us to login with a root password of our choosing. In this case, “password”

Now it’s time to run $ docker-compose up and sign into adminer. It takes a couple minutes to load up everything the first time.

Visit localhost:8080 to see the Adminer Sign in page.

Fill in the following fields:

system: mysql

server: db (This is the service name for mysql. If you had ‘teacup’ as the service name for the database in docker-compose.yml, you’d have ‘teacup’ in this field)

username: root (this is the default mysql user)

password: password (this is what we set for our MYSQL_ROOT_PASSWORD environment variable)

database: leave this blank.

Click “login” and you should be in! (If you get a “connection refused” error, it might not be finished loading) You can now create databases in Adminer and they should persist. Even if you shut everything down and start it back up, your databases will still be there.

Step 2. Volumes in a WordPress Docker environment

Volumes allow you to have persistent data. When you’re working with databases, the data needs to be stored somewhere so it can be used later. Volumes accomplish this goal.

What we setup above uses volumes, but they aren’t named, which can make things painful. In a terminal window, run $ docker volume ls to see all the volumes you have.

Every time Docker needs a volume, it’ll create one for you whether you name it or not. One of these volumes is where our data is stored for the project we created.

To figure out which one of these volumes is for our DB, you need to run $ docker ps to get the container name or ID of the database, (make sure the project is running first)

My “container name” for the DB is wptut_db_1

and run $ docker inspect -f ‘{{ .Mounts }}’ containername to see the volume id.

But this is a huge pain in the butt! There’s no need to torture yourself. We can just use Named Volumes.

Shut down the project with $ docker-compose down and add this code to your docker-compose.yml file.


    image: mysql
    volumes:    # add this line
      - wptut:/var/lib/mysql  # add this line too. other lines should already be there.

# at very bottom of docker-compose.yml
volumes: # not indented at all
  wptut:  #indented two lines

This allows us to use a pretty little volume called “wptut” instead of the slightly more difficult to remember 1991caa547d1d457fdab98ebfe8ea8621d7fb61722e1ed1837edac4e734b6c1b

Let’s shut down the project again, and start it back up with our new code added to docker-compose.yml.

Now run the $ docker inspect -f ‘{{ .Mounts }}’ containername and $ docker volume ls again to see your new and improved volume. This is where all your mysql data will be stored, and it’ll be much easier to work with than a huge number. My volume was created as wptut_wptut

Want More Tutorials?

Subscribe to our NewsLetter to get our latest Tutorials, Courses, product & tool reviews, and more! We don't email very often. When we do, it'll be good!

Step 3. Finally adding in WordPress to Docker

It’s time to add in WordPress to the mix. Add this to your docker-compose.yml file under “services”

    image: wordpress
      - 80:80

Now start up the project again, and visit localhost. You should be greeted with the typical WordPress installation page, and it will ask for all the necessary information. Unless you went ahead of me, we haven’t created a database for WordPress!

Go ahead and try without the database just for kicks and giggles, but it ain’t gonna work!

Go back to localhost:8080 (adminer), log in, and create a database for your wordpress site. I named mine ‘wordpress’ (On the home page of adminer near the top, click “create database”)

Now go install WordPress at localhost. Use the same credentials you used when signing into adminer.

We’re STILL not done. docker-compose completely creates and destroys containers when it’s started and stopped. We’d need to re-install wordpress every time because the install creates a wp-config.php file that ends up getting destroyed. We need a persistent wp-config.php file so we don’t have to install wordpress over and over again.

What we need to do is get a wp-config.php file onto our local filesystem so this is not an issue. I’m going to use a command $ docker cp to copy the wp-config file from the wordpress container into our local filesystem. If something goes wrong I’ll leave it up to you to grab a wp-config.php file from wherever.

Note: I also created a wptut/wordpress/ directory to place the wp-config.php file inside.

To get the wp-config.php file from our container to our local machine, we can run this:

$ docker cp wp-container-name:var/www/html/wp-config.php ~/path/to/project

which translates to the following for me:

$ docker cp wptut_wordpress_1:var/www/html/wp-config.php ~/Code/tuts/wptut/wordpress

That copies the wp-config file from the wptut_wordpress_1 container in the /var/www/html/ and places it on our local filesystem in my project directory: ~/Code/tuts/wptut/wordpress/

Our project directory should now look like this:

  - docker-compose.yml
  - /wordpress
    - wp-config.php

We need to mount that wp-config.php file into our wordpress container. If we were to use a volume to do this, then any files made by the container (ex. the entire wordpress project) would be copied into the wordpress directory on our local system. We DON’T want that.

Instead, we’re just going to add the wp-config.php file via the ‘COPY’ command. We’re also going to create a “Dockerfile” to accomplish this.


FROM wordpress

COPY wp-config.php /var/www/html/

Now, instead of just pulling the WordPress image straight from the Docker hub, we have to build our WordPress image from the Dockerfile instead.


    build: ./wordpress  # this line replaces the "image: wordpress" line.
      - 80:80

This builds the WordPress image from our Dockerfile, and takes the wp-config.php file and puts it in the /var/www/html directory. Now we should be able to start and stop the entire project without having to do any setup at all, because our wp-config.php file is already there.

Note: Because we’re using a Dockerfile, anytime we make a change to any Dockerfile, we have to “build” the image again. So now we need to start the project with this command:

$ docker-compose up –build (That’s two hyphens)

Step 4. Where to put Plugin and Theme Code

I always hated the tutorials that were like “whoom pow bsh! Here’s a wordpress environment!” and then didn’t tell you the most important part. WHERE DOES THE CODE GO!? Help a noob out!

Create a plugins directory in our project folder, along with a dummy plugin.


Plugin Name: Docker Hello
Description: Just another hello world but in Docker
Author: My Bad Self

The final project structure will look like this:

  - docker-compose.yml
  - /wordpress
    - Dockerfile
    - wp-config.php
  - /plugins
    - all your plugins go here 

This plugin folder will be where all of your plugin code goes, but we need to let docker-compose know about it:


    build: ./wordpress
      - ./plugins:/var/www/html/wp-content/plugins  # add this line!!!
      - 80:80

The line we added to our docker-compose.yml file grabs all the files and folders in our local “plugins” folder and mounts them into the container’s plugins folder. We’re now setup to edit code locally and have it automatically changed in our container. The best part is if you download plugins through the wordpress admin area, update or delete plugins, all of these changes can easily happen either way, and they’re synced. You can delete a plugin both in the dashboard, OR through the command line on your local machine.

The only problem you’re going to have is with permissions. So every once in a while you’re going to need to run a quick chmod and chown command on your plugins directory. If you need help with permissions. Here’s a complete guide to Linux/Unix Permissions

Get Full Code on Github

Homework assignment: Do the same thing you did with Plugin Development for themes! Or setup the whole thing in a wp-content directory.

This is probably a good place to stop. Be sure to check out Part TWO where we cover wp-cli and Unit Testing!

Learn to Code! Get Free Tutorials & Courses Straight to Your inbox!