How I use Docker and WordPress for website development

I enjoy building websites with WordPress as it is very quick and easy to install and modify. In order to make my development work easier, I prefer to utilize Docker containers as an even quicker and easier way to spool up a WordPress site locally and then deploy to an external hosting service when completed.

The tools

The four main portions of my development environment are the web server, the database server, database tools and my preferred editor.

I use the following prebuilt Docker images:

For editing of the PHP files, my preferred editor is VS Code from Microsoft. There are many editors out there, but I have found VS Code is very simple to use and organize your workspaces in. There are also many third party extensions to help with many types of coding. But we are going to keep it simple.

I choose to use Docker, as I can quickly spool up the various containers I need and keep everything sandboxed to those containers. This way I do not need to install all the different software applications on my host system. For instance, I can start up a MySQL server and when finished I can completely remove any trace of the server from my host computer. This way there are no lasting effects on my computer.

Installation of Docker is beyond the scope of this article. Please visit Docker Getting Started article for their own very well written guides to install and use Docker. Depending on your system, you may need to install Docker Compose separately. Compose will be required for this tutorial.

The setup

First, you will want to create a working folder that will store the important WordPress files, database files and the Docker Compose instruction file. This directory will be empty when we begin, but after creating the instruction file and the first run of the WordPress installation there will be two new folders with all the relevant user generated data of a WordPress installation.

Name this working directory anything that makes sense to you. For this tutorial I will use: mysite

Using your preferred editor, create a file called: docker-compose.yml

This file will contain all the instructions for Docker to download the required images (only done once), and then startup the containers using those images to host our WordPress website. Copy and Paste the following code into the docker-compose.yml file and then save to your working directory.

version: '3.3'

services:
  db:
    image: mysql:5.7
    volumes:
      - ./db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    volumes:
      - ./wp-content:/var/www/html/wp-content
    ports:
      - 8888:80
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORT: somewordpress

The explanation

Let’s breakdown the portions of the compose file. The first line identifies the version of Compose api that we are using. I have been using version 3.3 in all of my scripts since at least April 2018 without issue. As of November 2019 the latest version is 3.7

Next, we identify three services that Docker should startup. The names used for the services are arbitrary, you can name them anything you like as long as you do not use any reserved words that Compose would attempt to process

  • db – defines the database settings
  • wordpress – defines the WordPress settings
  • phpmyadmin – defines the settings to run phpMyAdmin

db – MySQL database

  db:
    image: mysql:5.7
    volumes:
      - ./db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress

The image I use is MySQL 5.7 from Oracle. This database has been tested and is commonly used for WordPress database backend. There are other SQL databases that can be used, one of which is even more popular called MariaDb. If you use a different database image, you may need to adjust your environment variables to work properly. Note: if you wish to use MySQL 8.0 there is an additional requirement to use the 5.7 version of password hashing. You will need to research how to accomplish this on your own. Following my suggestion of 5.7 above is easy and already tested.

I like to create the volume within my working folder so it is easier to zip up and transport as one working unit. The db_data volume stored on the host will be mapped to the /var/lib/mysql directory within the container. This way, if the container is stopped and destroyed you will retain the database the next time Compose restarts.

The environment variables are used by the image to setup custom settings to allow the other services to connect easier. You can supply the values you feel are appropriate for your use, as long as you ensure that the wordpress and phymyadmin services have matching values.

I could have setup a ports section to allow access to the database from outside of the container, but since I am including phpMyAdmin it was no longer necessary. I did open a port for the phpmyadmin service in order to get access to the database using this tool. If you want to use your own tool like MySQL Workbench, then supply a ports option where the first number is how you access from outside the container, and the second number is the port within the container that it maps to.

    ports:
      - 3306:3306

The above example will map from the host using localhost:3306 to allow access to the port 3306 within the container. The first number, choose any available port number you want (nothing below 1024), but the second number 3306 is used as default setup for MySQL server.

wordpress – WordPress files and PHP server

  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    volumes:
      - ./wp-content:/var/www/html/wp-content
    ports:
      - 8888:80
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress

The wordpress service depends upon the db service having been already started up. Adding the depends_on entry will tell Docker to wait for the database service to startup before creating the WordPress container. Thankfully MySQL starts up very quickly, otherwise we would need to add some extra commands to tell Docker to wait until a certain condition is reached in the database. ( How to tell Docker to wait I have never tried before, but noticed an option in some of the Compose tutorials. )

Again, we are mapping a volume to our working directory so we can safely destroy the Docker container and not loose the information we created. Note that we are only mapping the wp-content folder. This is because the other files and folders for WordPress are system files that are pertinent to the version of WordPress. The wp-content folder is where the user generated files are kept, like plugins, themes, and image uploads. We must keep a matching wp-content folder with a matching database folder, as the posts and pages are stored in the database, and any reference to which plugins and themes are installed. If we did not create these volumes, then when we restarted Docker we would need to run the WordPress Install every time.

I have mapped the host port of 8888 to the internal container port 80. The WordPress image uses the default port 80 within the container, so in order to access that port outside the container we need to map a number to that port. I chose to use port 8888 as that port will typically be open on my host computer and the 8000 range is typical for websites. This way I can type localhost:8888 in my web browser on my computer and see the WordPress website inside the container.

For the environment variables, we need to use the values according to our settings for the database service.

  • WORDPRESS_DB_HOST: db:3306
    • points to the service db and the port inside the container of 3306
  • WORDPRESS_DB_USER: wordpress
    • This is the same user as the MYSQL_USER in the database service
  • WORDPRESS_DB_PASSWORD: wordpress
    • This is the same password as the MYSQL_PASSWORD in the database service

phpmyadmin – phpMyAdmin web service

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORD: somewordpress

If you have been following along thus far, then this portion should almost be self explanatory. The important sections are the ports and environment variables.

I chose to use external port 3333 to map to the internal port 80 web service. This way I can access the web service but not confuse the ports with the WordPress web service above. I could have used a number like 8000 or 8080 or other typical web service port, but seeing that MySQL tends to use 3306 it was easier for me to remember the purpose of the external port by using a number close to what MySQL uses, so port 3333 seemed to make sense to me.

As for the environment variables:

  • PMA_HOST: db
    • db is the name of the database service that I used, so match that value here
  • MYSQL_ROOT_PASSWORD: somewordpress
    • This is the same password as the MYSQL_ROOT_PASSWORD password in the database service

The usage

To startup the Docker containers, open up a Terminal (Command Prompt) window and enter the directory where the docker-compose.yml file is stored. Then enter the following:

docker-compose up -d

Compose will read the instruction file, download the required images if not already on your system, startup the containers Docker Volumes and Networks.

Give Docker about a minute to fully startup the containers (usually takes less than a minute) and startup the WordPress services. Then open a web browser and enter http://localhost:8888/ for the URL.

The very first run, you will need to go through the WordPress 5-minute install (which really only takes about a minute). Then every other time you start Docker, you will already have your content ready for you, assuming you do not delete the db_data and wp-content folders in your working directory.

You may now edit any of the files in the wp-content folder of your working directory and see the changes affect your WordPress plugins and themes live. This is how I create my own themes and plugins. I will create a new subfolder under the themes or plugins folder in my working directory and edit the PHP files directly.

If you want to use phpMyAdmin to look at the database, then point your web browser to http://localhost:3333/ and then use the values from WORDPRESS_DB_USER and WORDPRESS_DB_PASSWORD from above in the WordPress service section. This is where you can export your database to a .sql file to be used for importing into a clean database on your new hosting server.

Using the exported .sql file and the matching wp-content folder, you can setup a fresh MySQL server, import the .sql file, and install the WordPress files and replace wp-content with your wp-content and you will have your development website now on a new hosting service. Note, that you may need to make some edits to your database for all the urls to work correctly, and that is beyond the scope of this tutorial.

If you are planning to migrate your work from your development server to a live host, you may be better of using one of the migration or backup plugins that are available for WordPress.

Troubleshooting

I see the following error:

ERROR: yaml.scanner.ScannerError: while scanning a simple key
  in "./docker-compose.yml", line 6, column 1
could not find expected ':'
  in "./docker-compose.yml", line 7, column 1

Docker Compose is very particular about spaces and indents. Sometimes, copying code and pasting into your editor will remove the indentation. Please be sure to indent your code per Docker Compose requirements.

I see the following error:

Traceback (most recent call last):
  File "docker-compose", line 6, in <module>
  File "compose/cli/main.py", line 71, in main
  File "compose/cli/main.py", line 124, in perform_command
  File "compose/cli/command.py", line 42, in project_from_options
  File "compose/cli/command.py", line 115, in get_project
  File "compose/config/config.py", line 387, in load
  File "compose/config/config.py", line 387, in <listcomp>
  File "compose/config/config.py", line 520, in process_config_file
  File "compose/config/config.py", line 229, in get_service_dicts
  File "distutils/version.py", line 46, in __eq__
  File "distutils/version.py", line 337, in _cmp
TypeError: '<' not supported between instances of 'str' and 'int'
[12417] Failed to execute script docker-compose

Sometimes when copy/paste, the incorrect quotes are pasted. Make sure using the simple single or double quotation marks in your document.

I see the following error:

ERROR: The Compose file './docker-compose.yml' is invalid because:
services.db.volumes contains an invalid type, it should be an array
services.wordpress.volumes contains an invalid type, it should be an array
services.wordpress.ports contains an invalid type, it should be an array
services.wordpress.depends_on contains an invalid type, it should be an array

Sometimes when copy/paste, the incorrect hyphen used in the volumes and ports descriptors. Make sure using the single dash and not the wider double-dash character. It is almost difficult to tell the difference between them, except the double dash is slightly wider. If you are using VS Code, the correct dash will be white and not red.

I see the following error:

ERROR: Couldn't connect to Docker daemon. You might need to start Docker for Mac.

Don’t forget to start the Docker application.

3 Comments

Add a Comment

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