Solutions

Option 3

Using WordPress Locally in Docker

Running Docker in WordPress is a super convenient way to develop in WordPress. You can easily spin up different containers for each theme or plugin you’re working on.

Different versions of WordPress can easily be tested against the same data. This workflow can save massive amounts of time, especially if you’re working for other clients.

There are a few gotchas. If you don’t set up your development environment correctly, the environment can be slow. Painfully, unusably slow, especially on a Mac.

This article will show you how to correctly set up your local environment and correct issues with existing environments.

Prerequisites:

  • Docker installed on your local development system. See the official Docker documentation for installation instructions on your OS.
  • docker-compose installed. Docker compose can be installed either with Docker itself or as a standalone application. Again see the official Docker documentation for the installation of docker-compose.

What is Docker?

Docker is an open-source platform that allows you to automate application deployment, scaling, and management using containerization. Containerization is a lightweight approach to virtualization, where you can package an application along with its dependencies into a standardized unit called a container.

A container includes everything needed to run an application, such as the code, runtime, system tools, libraries, and configurations. Docker provides a consistent environment for applications to run across different operating systems and infrastructures, making deploying and managing applications easier.

Critical concepts in Docker include:

  1. Docker Engine: The runtime environment that runs and manages containers. It consists of the Docker daemon, which manages containers, and the Docker client, which provides a command-line interface to interact with Docker.
  2. Images: Docker images are read-only templates that contain the application and its dependencies. Images are built from a set of instructions called a Dockerfile, which specifies the steps to create the image. Images can be shared and reused to create multiple containers.
  3. Containers: Containers are instances of Docker images that can be run on any system with Docker installed. Containers are isolated from each other and the underlying infrastructure, providing consistency and reproducibility. They are lightweight, start quickly, and can be easily scaled horizontally.
  4. Registry: A Docker registry is a repository for Docker images. The Docker Hub is a public registry containing a vast pre-built image collection. You can also set up private registries to store and share your images within your organization.
  5. Docker Compose: Docker Compose is a tool for defining and running multi-container applications. It allows you to define the services, networks, and volumes required for your application in a YAML file. You can start and manage the entire application stack with a single command.

Docker simplifies the process of packaging, distributing, and running applications by abstracting away the underlying infrastructure. It has gained popularity due to its flexibility, portability, and scalability, making it a widely adopted technology in both development and production environments.

docker-compose and folder organization

All containers will be orchestrated using docker-compose. Docker compose lets the user run a single command – usually without other options – to start and stop Docker containers. This saves the user from long, complicated invocations of Docker directly.

The compose application is configured using a file named ‘docker-compose.yaml‘. You can use a different filename, but then you have to specify it each time with the ‘-f’ option. Use the standard filename, it’s just easier.

The docker-compose.yaml file defines one or more services, each of which is a separate container running a different application. We’re going to use different YAML files for each application, each in their own subdirectory of a common parent directory. Since we’ll mostly be starting and stopping WordPress while keeping MySQL running, this approach makes the most sense.

The minor disadvantage of this approach is that we have to start each service and handle dependencies among containers manually. For this use case, we really only have one dependency: MySQL has to be running before WordPress.

How to run WordPress in Docker

The official WordPress Docker image comes in two varieties: Apache and FPM, and the difference is how PHP is run. The FPM variant requires a separate web server, so we’ll use the Apache variant. Running WordPress in conjunction with Apache is the most common method, regardless of whether Docker is used or not.

Regardless of which variant you use, WordPress needs a database, so we’ll run MySQL in a separate container. These two containers will use a dedicated network for communication. When containers use a dedicated network, Docker assigns each container a unique network name making its configuration much easier.

Step 1: Run MySQL and create a database

WordPress needs a database for its configuration, so we’ll start MySQL first and create it. You’re welcome to use an existing MySQL instance if one is available, but for this article, we’ll start a new instance.

Create a subdirectory for MySQL and change the directory to it:

mkdir mysql
cd mysql

Now create a docker-compose.yaml file containing the following contents.

version: '3.8'
services:
  mysql:
    container_name: mysql
    image: mysql:latest
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    networks:
      - local
    ports:
      - 3306:3306
    volumes:
      - ./data:/var/lib/mysql
    env_file:
      - .env

networks:
  local:
    name: local

This configuration uses the latest version of MySQL. For a production system, you’d want to pin the configuration to a specific, known version, but local development latest is fine.

The network is named local. Again, for production, you’d want to be more specific, but for local development, we often just want everything on a single, common network. Docker compose will assign the MySQL instance the name mysql on the local network, the same as the name of the service.

Environment variables are stored in a separate file name .env. The only environment variable the MySQL container needs is a default root password. Create the .env file similar to the following with any password you choose:

MYSQL_ROOT_PASSWORD=s0secret

The reason we create a separate .env file instead of including the environment directly in the YAML file is for source control. Often we’ll want to check docker-compose.yaml into source control, but we never want to check in a secret – even if it’s just for local configuration. This approach allows us to commit the YAML file and keep the .env file locally only.

Create the network and start the container via:

docker-compose up -d

All database data is stored in the data subdirectory.

The MySQL container also contains the mysql client which we can use to create the initial WordPress database.

docker-compose run --rm mysql mysql -h mysql -p -e 'CREATE DATABASE wordpress'

You’ll be prompted for the password from your environment file, then a database name ‘wordpress’ will be created.

Step 2: Configure and start WordPress in Docker Container

Now we’re ready to create the WordPress container.

Create another subdirectory in the same folder (alongside the mysql subdirectory) for the WordPress configuration.

mkdir wordpress
cd wordpress

The docker-compose.yaml file will look like:

version: '3.8'

services:
  wordpress:
    container_name: wordpress
    image: wordpress:latest
    volumes:
      - ./data:/var/www/html
    ports:
      - "80:80"
    env_file:
      - .env
    networks:
      - local
    tty: true
    stdin_open: true

networks:
  local:
    name: local
    external: true

The same approach is used as for MySQL, except the network is marked as external, telling Docker to connect the new container to the network we defined previously. The network has to exist for this to work, which ensures we’ve started the database already.

The WordPress container accepts most of the wp-config.php options as environment variables, so we don’t have to edit that file directly. Here is a minimal .env file for this container:

WORDPRESS_DB_HOST=mysql:3306
WORDPRESS_DB_USER=root
WORDPRESS_DB_PASSWORD=s0secret
WORDPRESS_DB_NAME=wordpress

We’re using the root username and password to connect to the database, which again is okay for development, but for production, you’d want to use a dedicated user.

We can use the hostname ‘mysql’ since we’re using a dedicated network, and Docker assigned it for us.

Start it via docker-compose up -d as before. Once it’s running, you’re ready to configure WordPress by the usual 5-minute installation in your browser.

Open a browser, navigate to http://localhost, and you’ll see the standard installation screen asking you to choose your language.

Troubleshooting WordPress in Docker

Docker is a complicated application, and sometimes things go wrong.

Why is WordPress really slow on a Mac?

Older versions of Docker were really slow when accessing bind mounts – where the application stores data in a filesystem folder like the data directories above.

You can fix this in Docker Desktop by changing your file-sharing settings. Click on Settings then in the General tab, choose ‘VirtioFS’.

File sharing settings to change that fix Docker slowness.

There are numerous Stack Overflow answers suggesting other solutions. At the time of this writing, those proposed solutions are all incorrect.

Warning: make sure you back up MySQL databases because of the following.

MySQL won’t start after changing Docker file sharing

If you change the file-sharing implementation, you may run into the following (depending on the original setting):

Different lower_case_table_names settings for server ('2') and data dictionary ('0').
Data Dictionary initialization failed.
Aborting

This is because Mac OS file systems are case-sensitive, and the way MySQL stores files is incompatible with the new setting.

The good news is that you can just reset the file-sharing implementation to the original value, and MySQL will work again. The not-so-good news is that you have to back up and restore any databases you want to keep.

If this is really a development system, the easiest thing to do is just stop everything, delete the data directories, and re-install MySQL and WordPress. (Note: all your data is destroyed, be sure this is what you want to do.)

Final Thoughts

Docker is a great way to do local WordPress development, and we’re sure using it will improve your development process. At Mindspun, it’s how we do all our development. Happy coding.

Subscribe for Email Updates

Everything an ambitious digital creator needs to know.