Docker for Local WordPress Development


April 8, 2023

WordPressWeb Development

What’s Docker?

Great question. Fortunately, someone who was paid to make this sound good has already answered this:

Docker takes away repetitive, mundane configuration tasks and is used throughout the development lifecycle for fast, easy and portable application development.

From the docker.com homepage

As it pertains to this guide, Docker is a tool that will enable you to build portable, reproducible local development environments for your WordPress projects. Bravo.

But, Why?

  • Ease of use. Once configured, using it is as simple as one command to start the environment when you begin work, and another command to stop it when you are finished for the day.
  • Simple LAMP stack setup. This guide will provide us with ye olde reliable LAMP stack for WordPress, but Docker can be used for many different environments. For example, you could use NGINX instead of Apache, or MariaDB instead of MySQL.
  • When paired with version control, Docker allows us to keep environment parity between developers. Gone are the days of, “Well it worked on my machine.” A good analogy is that a Docker container is like a picnic basket, which contains all the food and the dishes for the picnic, or your app. Anyone with this picnic basket could have the same picnic.
  • Docker can also be used to keep parity between your local, staging, and production environments, although we are not going to cover that here.

Prerequisites

To kick it off, head on over to the Docker website and download a copy of Docker Desktop. Install that.

This process should work on macOS or Windows with WSL2. I’ve tested it on both, but your mileage may vary, particularly with Windows.

Setup

It’s crazy easy. Just create your project directory. Then, inside of it, add the following docker-compose.yml file.

version: '3.1'

services:

  # mysql database container
  db:
    image: mysql:5.7.16
    volumes:
      - ./db:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: exampledb
      MYSQL_USER: user
      MYSQL_PASSWORD: pass

  # wordpress container
  wordpress:
    depends_on:
      - db
    image: wordpress
    volumes:
      - ./wp-content:/var/www/html/wp-content:cached
    ports:
      - 8080:80
    restart: always
    environment:
      WORDPRESS_DEBUG: 1
      DEVELOPMENT: 1
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: pass
      WORDPRESS_DB_NAME: exampledb

Save that, pop open your terminal, and in your project directory, run:

docker-compose up

This will run for a minute as it builds your container for the first time. You’ll know it’s complete when the terminal stops printing. The final output will likely be:

Version: '5.7.16' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)

Once finished, you can navigate to localhost:8080 in your browser. From here you can go through the WordPress installation… and you’re in. All done, kbye!

Wait, What just happened?

The docker-compose.yml file acts as the instructions to build our containers. Let’s break it down:

version: '3.1'

version defines the docker-compose version we are using. 3.1 is fine for this purpose. Read more about the versions here.

services:
  # mysql database container
  db:

  # wordpress container
  wordpress:

services are how we define the containers that will be build. We have two:

  • db for MySQL
  • wordpress for Apache, PHP, and WordPress.

db service

    image: mysql:5.7.16

image is where we define the base image we want to use for our database. MySQL is popular, but you can also use MariaDB. I’ll note that I’ve used MySQL 5.7.16 for compatibility with Windows, but 5.7 worked fine for me on macOS.

    volumes:
      - ./db:/var/lib/mysql

volumes is where we define the bind mount for the database which ensures persistence across restarts of the container. Basically this makes sure you don’t lose your data.

    restart: always

restart: always as a precaution.

    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: exampledb
      MYSQL_USER: user
      MYSQL_PASSWORD: pass

environment is where we define the environment variables for the container, such as the database name, MySQL user, and their password.

wordpress service

    depends_on:
      - db

depends_on specifies the database container as a dependency and ensures it is started first.

    image: wordpress

image: wordpress will pull the latest stable version of WordPress. Note that you may want to specify a WordPress version here so that you can ensure compatibility of your project when WordPress updates. To specify the version, use a tag with the version number. For example: image: wordpress:6.1. Here is a list of all of the various image tags for WordPress.

    volumes:
      - ./wp-content:/var/www/html/wp-content:cached

volumes is where we add the bind mount for the /wp-content directory. Read more about this bind mount below. I’ve added a consistency of cached, which in my experience can make a significant improvement in performance if you’ve got a lot in the /wp-content directory.

    ports:
      - 8080:80

ports: 8080:80 maps our local port 8080 to the container’s port 80 where WordPress is running, allowing us to access it locally through 8080.

    restart: always

restart: always once again.

    environment:
      WORDPRESS_DEBUG: 1
      DEVELOPMENT: 1
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: pass
      WORDPRESS_DB_NAME: exampledb

And finally, we define the environment variables for WordPress. debug and development set as 1 for a local development environment with debug mode enabled. Followed by the database variables, which must match those defined in the db service’s environment variables.

Bind Mounts

The container has a bind mount configured between the /wp-content directory in the project folder and the /wp-content directory in the container. This ensures that the files in these directories will remain in sync as you change them locally, or in the container. This will allow you to build your custom theme right here in your project’s /wp-content/themes directory. In short, changes you make locally are reflected in your running container, which you can access at localhost:8080.

Working with Docker

Now that your container is running, you can start developing your theme. Any uploads to WordPress will be saved in the /wp-content/uploads directory, and all changes to your CMS content will be tracked in the database in the /db folder.

docker-compose stop

When you’re finished for the day and you want to stop your local environment, run docker-compose stop. This will stop the running containers.

docker-compose start

When it’s time to fire up the dev environment again, run docker-compose start to start your previously stopped containers.

docker-compose down

docker-compose down is similar to docker-compose stop, except that down removes the containers after stopping them. The reason I usually prefer a stop and start workflow over down and up is that it does not produce a bunch anonymous volumes every time the container goes down. While seemingly trivial, these volumes can slowly build up, taking up space and slowing your Docker containers down. If you run into that situation try a docker volume prune.

Adding Version Control

The portability of Docker really shines when you combine it with version control like Git. By adding a docker-compose.yml file to your git repo, you are ensuring that not only is your app under version control, but so is the environment in which it runs. This can be tremendously helpful for projects with more than one developer.

So, go ahead and throw the whole thing into a git repo, but you’ll probably want to add a .gitignore to prevent some files from being tracked. This will vary based on your requirements but you might want to consider including the following:

/db
/wp-content/uploads
/wp-content/plugins

/db will exclude the database from the repo. The database can be huge and will result in lots of git changes if it is included in the repo. If you need to share your database with other developers, I’d consider using a different option (maybe a plugin or WP-CLI).

/wp-content/uploads will exclude Media Library uploads from the repo.

wp-content/plugins will exclude the plugins from the repo. This way you’re not managing plugin updates through version control.

Taking it further

And there it is. Hopefully you’re now in a good place to be able to start using Docker to build portable local environments for your WordPress projects. And now that you’ve had just a wee taste of what Docker can do, let me give you some thoughts to chew on.

Project Skeletons

Many experienced WordPress developers will use the same custom starter theme and/or setup to begin every WordPress project. You should too! And you can bake a Docker configuration right into it. Just think of how much time could be saved with a workflow that solely consists of cloning your Skeleton’s repo into your new project, and running docker-compose up. Now you immediately have a pre-configured local development environment for your new project. You can move straight into the fun parts. You’re getting a raise for that one. Good job.

Make it rain

Deployments

I’m not going to get into it here, but it is good to be aware that some web hosts will support Docker containers. You can write a set of instructions for your environments as Dockerfiles, which will afford parity between your local, staging, and prod environments, a level of predictability that can otherwise be hard to come by.

Leave a Reply

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

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.