Skip to main content

WordPress Development using Docker CLI

Introduction

In this article I’ll walk through the set up of a WordPress development environment using Docker Command Line Interface (CLI) to create a WordPress web server and a MySQL database. 

I find that running WordPress locally is very useful for:

  • developing WordPress themes,
  • developing WordPress plugins,
  • trying third party themes and plugins,
  • developing WordPress itself,
  • writing Posts and Pages, and
  • learning WordPress.

Most of the examples below use Windows command line and shell scripts, but these commands will work on Mac or Linux (*nix) with little or no change.

The completed source code, including both Windows and *nix versions of the scripts, is available on GitHub (https://github.com/Kajabity/My-Cheese-Blog).  

I’ve written a related article, How to use Docker Compose for WordPress Development that you may also find useful.

Add a comment below if you have any questions.

WordPress and MySQL

To develop WordPress themes, plugins and content locally, you need:

  • A computer with Docker Desktop installed.
  • A web server such as Apache HTTPD, or NGINX or Microsoft IIS.
  • A PHP interpreter to run the PHP code for the web server.
  • The WordPress application files.
  • A MySQL database, or equivalent.
A laptop development computer accessing a Web Server, running Apache HTTPD with PHP and a WordPress application files, which connects to a MySQL database.
WordPress web server and MySQL database

We are going to use the following two official Docker images:

  1. MySQL – where WordPress will store content and settings, and
  2. WordPress – with both Apache HTTPD and PHP pre-installed.

With these images, I’m going create the following:

  • A user-defined network within the Docker engine to attach the Docker containers to.
  • A named volume to retain MySQL data between sessions.
  • A host directory to keep the WordPress code safe between sessions.
  • A MySQL database container.
  • A WordPress container with Apache HTTPD web server and PHP runtime.

The finished setup is illustrated in the following diagram:

WordPress and MySQL Docker Containers for My Cheese Blog running on Localhost with a user-defined bridge network. A data volume is attached to the MySQL Container and a directory on the host computer is attached as the WordPress container content folder. Illustrative IP addresses are also shown.
WordPress and MySQL Docker containers on localhost with a user-defined network

The IP addresses are only for illustration and may differ between different computers or even different runs on the same computer.

If you haven’t installed Docker yet, now’s a good time visit Get Docker, and then ensure Docker is running. You will also need to sign-up for a free Docker Hub account.

Project Setup

As a test project, I’m going to create a new Blog project called “My Cheese Blog”. After all, who doesn’t enjoy a nice bit of strong cheddar?

The project directory is C:\projects\wordpress-docker\My-Cheese-Blog.

I will need to use quite a lot of Docker commands – so I’ll put them in a set of (Windows/*nix) scripts for convenience:

  • create (.bat or .sh) – create the network, volume and containers.
  • stop (.bat or .sh) – stop the containers temporarily.
  • restart (.bat or .sh) – restart the stopped containers.
  • purge (.bat or .sh) – tear down the network and containers again.

Note: I’ve selected these names so you can use autocomplete on the command line with a single letter…

Now is also as good a time as any to create a couple of other files for later:

  • html/ – a content directory where WordPress application files will be installed.
  • README.md – always good to summarise the project for others (or yourself in a month’s time – I’ve been there!)

The stop and restart scripts are not really essential, but are nice to have as they enable you to free up CPU time while you switch to other tasks.  Still, if you run the purge script to remove the containers and networks, the data volume and files will still be there next time you run the create script – unless you delete them yourself.

Here is an illustration of the project so far (my Windows version) – I’ve used JetBrains IntelliJ IDEA here, but any other IDE or editor will be just fine.

A screenshot of JetBrains IntelliJ IDEA IDE with the My Cheese Blog project and files.
Screenshot of JetBrains IntelliJ IDEA with WordPress and MySQL development scripts for Docker.

Network Setup

The WordPress container will need to connect to the MySQL database and they will do this using a virtual network within Docker.

Docker provides a default bridge (internal) network called, unsurprisingly “bridge”.  Unfortunately, it doesn’t support automatic service discovery – which to you and me, means that you can’t use container names to connect between them.

We could use the “–link” parameter to push a name for the MySQL container into the WordPress container – but it is now considered to be legacy and it’s best to avoid anything that might stop working unexpectedly. 

Another alternative is to connect them using the container’s IP address.  This would work but the IP address is liable to change each time the container starts and it would be annoying to have to keep updating it.

Instead, create a user-defined bridge network and attach the containers to it.  User-defined networks support automatic service discovery – enabling each container to reference the others on the same network using their container names which are their hostnames.

To learn more about Docker networking have a look at the Docker Networking overview and to understand Docker bridge networks see Networking with standalone containers.

Open create.bat (or create.sh for the Mac or Linux version) and add the following line:

docker network create --driver bridge cheese_nw

Next, open purge.bat (or purge.sh for the Mac or Linux version) add the following line to remove the network:

docker network rm cheese_nw

If you’re feeling keen, you can save both files and test them now:

C:\projects\wordpress-docker\My-Cheese-Blog>.\create.bat

C:\projects\wordpress-docker\My-Cheese-Blog>docker network create --driver bridge cheese_nw
30a85bbe8d1b71e94807bc59f4563c9c16d0701713c56d1b20fafde7c59ee299

C:\projects\wordpress-docker\My-Cheese-Blog>docker network ls
NETWORK ID     NAME        DRIVER    SCOPE
43ee80201828   bridge      bridge    local
30a85bbe8d1b   cheese_nw   bridge    local
44f22fd370f9   host        host      local
f073bb80b876   none        null      local

C:\projects\wordpress-docker\My-Cheese-Blog>.\purge.bat

C:\projects\wordpress-docker\My-Cheese-Blog>docker network rm cheese_nw
cheese_nw

C:\projects\wordpress-docker\My-Cheese-Blog>docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
43ee80201828   bridge    bridge    local
44f22fd370f9   host      host      local
f073bb80b876   none      null      local

C:\projects\wordpress-docker\My-Cheese-Blog>

Here, the create.bat script created the user-defined network cheese_nw which you can see using the docker network ls command. Then the purge.bat command removed the network again.

Note: the Windows scripts output a lot of mess that the Mac/Linux version doesn’t.  In the GitHub version I added @echo off to hide it all, but thought it useful to see the inner workings here.

If you see an error, it may be that you haven’t started Docker Desktop yet, or that the network already exists (perhaps you ran the create script twice).

Use docker network ls to list the available docker networks – both times the three default networks are listed (bridge, host, local) with their ID and type.  After running the create script, there is also a cheese_nw user-defined bridge network.

There is no need to stop or restart the network as it uses very little resource on its own.

Volume Setup

When Docker runs an image, the container includes a copy of the image as the root disk.  Docker allows you to mount paths inside the container to external Volumes. It’s similar to mounting a network shared drive. 

A volume can be either a separate storage area inside the Docker engine, or mapped to a host folder enabling you to view or access the files on the host computer.

While any path in the container can be mapped to a volume, some images deliberately publish useful paths as volumes. For example, the MySQL image publishes the path where database data files are stored (/var/lib/mysql), while the WordPress image publishes the Apache HTTPD web server content path (/var/www/html).

If you do nothing with these volumes when you start the images, Docker will create new, unnamed volumes that persist even after the containers have been removed.  They are just clutter that needs to be pruned occasionally. 

I’ll make use of both of them in the scripts to help with development.

First, create a user-defined volume named “cheese_data” inside Docker for the MySQL data so that it is persisted between container creations and deletions, and computer restarts.

Add the following command to the create script:

 docker volume create cheese_data

Again, there’s no need to stop or restart the volume – and the purge script shouldn’t remove it. This is the precious WordPress configuration and content and should be retained.

Instead, when you have definitely finished with the data in the volume or if you just want to blow it away and start again, you can issue the following command on a command line (after purging the containers):

docker volume rm cheese_data

Note: If you delete this volume, you will need to go through the database and WordPress installation processes again next time you create the images.

I’ll map the content volume in the WordPress container to the .\html folder in the project when I create the container.  This will enable easy access to the WordPress content files on the host computer.

Now you can test the create script again:

C:\projects\wordpress-docker\My-Cheese-Blog>.\create.bat

C:\projects\wordpress-docker\My-Cheese-Blog>docker network create --driver bridge cheese_nw
6b6f87ce91b0a55ae9f0fcc68cd1db3464efb1c2be6155996eb6d0db4d44a3fa

C:\projects\wordpress-docker\My-Cheese-Blog>docker volume create cheese_data
cheese_data

C:\projects\wordpress-docker\My-Cheese-Blog>docker volume ls
DRIVER    VOLUME NAME
local     cheese_data
local     my-tomato-blog_data

C:\projects\wordpress-docker\My-Cheese-Blog>

There’s no change to the stop, restart and purge scripts.

Database Container

According to the WordPress Requirements it needs either a MySQL (version 5.6 or greater) or MariaDB (version 10.1 or greater) database to run. 

Here, I’ve used the official MySQL 5.7 Docker image as the latest version (version 8) tends to be a bit slow on my 10-year-old laptop!  You can also find details of the official MariaDB image on Docker Hub.

Note: Later versions of MySQL require you to use 127.0.0.1 as the hostname instead of localhost when connecting from the host computer.

I’ve added environment variables to tell the MySQL container to create a database schema for WordPress along with a database username and password so that it can access it.

For the Windows version, open create.bat and append the following lines:

set DB_ROOT_PWD=verySecret
set DB_NAME=wp_data
set DB_USER=wp_user
set DB_PWD=Camembert

docker run ^
    --name cheese_db ^
    --network cheese_nw ^
    --env "MYSQL_ROOT_PASSWORD=%DB_ROOT_PWD%" ^
    --env "MYSQL_DATABASE=%DB_NAME%" ^
    --env "MYSQL_USER=%DB_USER%" ^
    --env "MYSQL_PASSWORD=%DB_PWD%" ^
    --publish 3306:3306 ^
    --volume cheese_data:/var/lib/mysql ^
    --detach ^
    mysql:5.7

The command is the same for the Mac or Linux version – but the script syntax is different so open create.sh and append the following lines:

DB_ROOT_PWD=verySecret
DB_NAME=wp_data
DB_USER=wp_user
DB_PWD=Camembert

docker run \
    --name cheese_db \
    --network cheese_nw \
    --env "MYSQL_ROOT_PASSWORD=${DB_ROOT_PWD}" \
    --env "MYSQL_DATABASE=${DB_NAME}" \
    --env "MYSQL_USER=${DB_USER}" \
    --env "MYSQL_PASSWORD=${DB_PWD}" \
    --publish 3306:3306 \
    --volume cheese_data:/var/lib/mysql \
    --detach \
    mysql:5.7

Both versions include environment variable definitions for the user names, passwords and database name as these are needed for both the MySQL and WordPress containers.

It also makes it easier to externalise them so you don’t commit them to source control.

Next, the “docker run” command includes the following parameters:

  • --name cheese_db – name the container “cheese_db” rather than let Docker pick a random name.
  • --network cheese_nw – attach the container to the user-defined network.
  • --env "MYSQL_ROOT_PASSWORD=${DB_ROOT_PWD}" – passes the MySQL root password to the container as an environment variable.
  • --env "MYSQL_DATABASE=${DB_NAME}" – passes a WordPress database schema name to the container as an environment variable so that MySQL will create it on startup.
  • --env "MYSQL_USER=${DB_USER}"  – passes the WordPress database user name to the container as an environment variable so that MySQL will create it on startup.
  • --env "MYSQL_PASSWORD=${DB_PWD}" – passes the WordPress database user password to the container as an environment variable.
  • --publish 3306:3306 – publishes container port 3306 (the default for MySQL) to the host so that you can access it outside Docker.
  • --volume cheese_data:/var/lib/mysql – mounts the MySQL container data path to the user-defined volume created above.
  • --detach – causes the container to run in the background (detached) instead of sending logs too the command line until you kill it.
  • mysql:5.7 – runs MySQL image version 5.7.

These are the long versions of these parameters for clarity, but there are short forms for some.  See the docker run reference for details.

I also added commands to the stop and restart scripts to put the containers to sleep (freeing up CPU time), and to wake them again.

Open stop.bat (or stop.sh for the Mac or Linux version) and add the following line:

docker stop cheese_db

Open restart.bat (or restart.sh for the Mac or Linux version) and add the following line:

docker start cheese_db

Finally, to remove the database container (without removing the data) open purge.bat (or purge.sh for the Mac or Linux version) and insert the following two lines at the top of the file:

docker stop cheese_db
docker rm cheese_db

You need to stop and remove the cheese_db container before removing the network or Docker will complain that the network still has active endpoints.

Test the MySQL Container

Again, you can test the create script.  If this is the first time you have used the MySQL image, it will need to pull (download and extract) the image layers from Docker Hub – for example:

A screen shot of a Windows command shell while running create.bat showing Docker downloading MySQL image layers.
Docker downloading MySQL image layers on Windows

This can take a couple of minutes, but once the image has been created and started, the container ID is output:

...
53ee1339bc3a: Pull complete
b0c0a831a707: Pull complete
Digest: sha256:7cf2e7d7ff876f93c8601406a5aa17484e6623875e64e7acc71432ad8e0a3d7e
Status: Downloaded newer image for mysql:5.7
7f893264d5ab940adfd243dacc2a3d953e67f3d1b098c9a5c06fe30461a21481

You can check that the container is running using the docker ps command:

C:\projects\wordpress-docker\My-Cheese-Blog>docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                                                  NAMES
7f893264d5ab   mysql:5.7   "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   cheese_db

C:\projects\wordpress-docker\My-Cheese-Blog>

The container will initialise the new database when it first starts.  This will be in the cheese_data volume that is attached to the /var/lib/mysql container path.

This can take a while.

Check the logs using docker logs cheese_db to see if start-up is finished:

C:\projects\wordpress-docker\My-Cheese-Blog>docker logs cheese_db
2021-08-21 23:36:38+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.35-1debian10 started.
2021-08-21 23:36:38+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2021-08-21 23:36:38+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.35-1debian10 started.
2021-08-21 23:36:38+00:00 [Note] [Entrypoint]: Initializing database files
…
…
2021-08-21T23:37:32.239121Z 0 [Note] Event Scheduler: Loaded 0 events
2021-08-21T23:37:32.239726Z 0 [Note] mysqld: ready for connections.
Version: '5.7.35'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)

C:\projects\wordpress-docker\My-Cheese-Blog>

There are several pages of junk (including a few warnings – MySQL muttering to itself…) so I’ve shortened it a lot, but it’s the final two lines you are looking for – particularly: mysqld: ready for connections.

If you now stop and restart the container – or even purge and create it again – the start-up should be much faster as the image has already been pulled and cached locally, and the database files are already initialised in the cheese_data volume.

But, before that, it might be a good idea to check that the database is working as expected.

Accessing the MySQL Database

There are several ways to access the database:

  • Use MySQL Workbench a database IDE from MySQL.  There are also many other database tools that support MySQL and MariaDB.
  • Use your IDE – many have database access built in (Eclipse, Visual Studio, JetBrains IntelliJ IDEA, etc.).
  • Use a database admin Docker image such as phpMyAdmin or Adminer (see later).
  • Start another MySQL container to use the command line client – see https://hub.docker.com/_/mysql in the section named Connect to MySQL from the MySQL command line client.

To access the database you will need the following connection details (assuming you followed the example above):

  • Hostname: see below.
  • Port: 3306 (default for MySQL).
  • User: root or wp_user
  • Password: “verySecret” for root, or “Camembert” for wp_user.
  • Database schema (optional): leave blank, or use wp_data.

For the hostname, you will need to use:

  • cheese_db to connect from another Docker container on the same user-defined network (e.g., phpMyAdmin or Adminer).
  • localhost if you use older versions of MySQL or MariaDB.
  • 127.0.0.1 for newer versions of MySQL or MariaDB.
A screenshot of the MySQL Workbench view of the empty WordPress database schema.
MySQL Workbench view of empty WordPress database schema

If all is well, you should see the WordPress database schema (wp_data) with no existing tables so far.

If you have any problems creating the MySQL container or connecting to the database, see the troubleshooting section later.

Before moving on, it’s time to clean up.  Run the purge script to remove the MySQL container:

C:\projects\wordpress-docker\My-Cheese-Blog>.\purge.bat

C:\projects\wordpress-docker\My-Cheese-Blog>docker stop cheese_db
cheese_db

C:\projects\wordpress-docker\My-Cheese-Blog>docker rm cheese_db
cheese_db

C:\projects\wordpress-docker\My-Cheese-Blog>docker network rm cheese_nw
cheese_nw

C:\projects\wordpress-docker\My-Cheese-Blog>

WordPress Container

For the WordPress container, I’ll use the official Docker WordPress image.

This image includes an Apache HTTPD server with PHP installed.  The web server publishes the contents of container path /var/www/html and this is where it will install WordPress, if not already present.

The image also exports this path a volume and it is often mapped to either an internal Docker volume, or to a host folder.

Apache HTTPD uses port 80 for HTTP by default but, as there is often a web server on that port already, I usually map it to port 8080.

Finally, the WordPress image needs an existing MySQL database schema and the username and password details to access it.  Use the environment variables defined above to pass the details to the WordPress container.

For the Windows version, open create.bat and append the following lines:

set DB_PREFIX=cheese_
set WP_CONTENT=C:\projects\wordpress-docker\My-Cheese-Blog\html

docker run ^
    --name cheese_web ^
    --network cheese_nw ^
    --publish 8080:80 ^
    --volume "%WP_CONTENT%:/var/www/html" ^
    --env "WORDPRESS_DB_HOST=cheese_db" ^
    --env "WORDPRESS_DB_NAME=%DB_NAME%" ^
    --env "WORDPRESS_DB_USER=%DB_USER%" ^
    --env "WORDPRESS_DB_PASSWORD=%DB_PWD%" ^
    --env "WORDPRESS_TABLE_PREFIX=%DB_PREFIX%" ^
    --detach ^
    wordpress

Alternatively, for the Mac or Linux version open create.sh and append the following lines:

DB_PREFIX=cheese_
WP_CONTENT=~/projects/wordpress-docker/My-Cheese-Blog/html

docker run \
    --name cheese_web \
    --network cheese_nw \
    --env "WORDPRESS_DB_HOST=cheese_db" \
    --env "WORDPRESS_DB_NAME=${DB_NAME}" \
    --env "WORDPRESS_DB_USER=${DB_USER}" \
    --env "WORDPRESS_DB_PASSWORD=${DB_PWD}" \
    --env "WORDPRESS_TABLE_PREFIX=${DB_PREFIX}" \
    --publish 8080:80 \
    --volume "${WP_CONTENT}:/var/www/html" \
    --detach \
    wordpress

As you can see, in both versions I’ve added a couple more environment variables – DB_PREFIX is used by WordPress to prefix the name of every table it creates (including any added by plugins, etc.) so that multiple instances of WordPress can share the same database schema. 

WP_CONTENT is used to define the host computer content folder so that we can map the Apache HTTPD content volume to it.  On my Windows computer it’s C:\projects\wordpress-docker\My-Cheese-Blog\html, while in the Linux version I used ~/projects/wordpress-docker/My-Cheese-Blog/html.

The next command uses the “docker run” command to start the WordPress container with the following parameters:

  • --name cheese_web – name the container rather than let Docker pick a random name.
  • --network cheese_nw – attach the container to the user-defined network.
  • --env "WORDPRESS_DB_HOST=cheese_db" – passes the name of the MySQL container as an environment variable.
  • --env "WORDPRESS_DB_NAME=${DB_NAME}" – passes the WordPress database schema name to the container as an environment variable.
  • --env "WORDPRESS_DB_USER=${DB_USER}" – passes the WordPress database user name to the container as an environment variable.
  • --env "WORDPRESS_DB_PASSWORD=${DB_PWD}" – passes the WordPress database user password to the container as an environment variable.
  • --env "WORDPRESS_TABLE_PREFIX=${DB_PREFIX}" – passes the WordPress database table prefix to the container as an environment variable.
  • --publish 8080:80 – publishes container port 80 (the default for HTTP) to host port 8080 so that you can access it outside Docker.
  • --volume "${WP_CONTENT}:/var/www/html" – mounts the ./html host folder to the WordPress container content path so that you can work on WordPress and other website files.
  • --detach – causes the container to run in the background (detached) instead of sending logs too the command line until you kill it.
  • wordpress – runs the latest WordPress Docker image.

Next, the stop and restart scripts need to be updated.

Open stop.bat (or stop.sh for the Mac or Linux version) and insert the following line before stopping the database:

docker stop cheese_web

Open restart.bat (or restart.sh for the Mac or Linux version) and append the following line after starting the database:

docker start cheese_web

Finally, to remove the WordPress container (without removing the content files) open purge.bat (or purge.sh for the Mac or Linux version) and insert the following two lines at the top of the file, above where the dB container is stopped and removed:

docker stop cheese_web
docker rm cheese_web

Note: It’s important that the commands in the stop and purge scripts are in the reverse order to those in the create and restart scripts so that there are no errors about resources still being in use when you run them.

Test the WordPress Container

You can test the create script once more (we’re nearly finished).

C:\projects\wordpress-docker\My-Cheese-Blog>.\create.bat

C:\projects\wordpress-docker\My-Cheese-Blog>docker network create --driver bridge cheese_nw
25a1b067e7f1af99c7f02a8a4bb844aa5b630bf52587e8559e607ab905b92ca3

C:\projects\wordpress-docker\My-Cheese-Blog>docker volume create cheese_data
cheese_data

C:\projects\wordpress-docker\My-Cheese-Blog>set DB_ROOT_PWD=verySecret

C:\projects\wordpress-docker\My-Cheese-Blog>set DB_NAME=wp_data

C:\projects\wordpress-docker\My-Cheese-Blog>set DB_USER=wp_user

C:\projects\wordpress-docker\My-Cheese-Blog>set DB_PWD=Camembert

C:\projects\wordpress-docker\My-Cheese-Blog>docker run     --name cheese_db     --network cheese_nw     --env "MYSQL_ROOT_PASSWORD=verySecret"     --env "MYSQL_DATABASE=wp_data"     --env "MYSQL_USER=wp_user"     --env "MYSQL_PASSWORD=Camembert"     --publish 3306:3306     --volume cheese_data:/var/lib/mysql     --detach     mysql:5.7
57aec3d69c9c756a18e51c5f5b15f7266cbfdc40c2fd3d9ea1dc73cfe5cf225d

C:\projects\wordpress-docker\My-Cheese-Blog>set DB_PREFIX=cheese_

C:\projects\wordpress-docker\My-Cheese-Blog>set WP_CONTENT=C:\projects\wordpress-docker\My-Cheese-Blog\html

C:\projects\wordpress-docker\My-Cheese-Blog>docker run     --name cheese_web     --network cheese_nw     --publish 8080:80     --volume "C:\projects\wordpress-docker\My-Cheese-Blog\html:/var/www/html"     --env "WORDPRESS_DB_HOST=cheese_db"     --env "WORDPRESS_DB_NAME=wp_data"     --env "WORDPRESS_DB_USER=wp_user"     --env "WORDPRESS_DB_PASSWORD=Camembert"     --env "WORDPRESS_TABLE_PREFIX=cheese_"     --detach     wordpress
Unable to find image 'wordpress:latest' locally
latest: Pulling from library/wordpress
99046ad9247f: Already exists
3875fa64ab1e: Already exists
e9329a8f553a: Already exists
9bb327f9b0a4: Already exists
051b56f0e6a3: Already exists
da02d3111b48: Already exists
98ca514d99e4: Already exists
272eb89cf995: Already exists
52c4f1832f9f: Already exists
9a25e9de25de: Already exists
5e38c5236618: Already exists
5157b271195a: Already exists
b2e044a4a66e: Already exists
063c8617c94d: Pull complete
746ca3572fc4: Pull complete
c3cc7af90188: Pull complete
471d9a1cc4b5: Pull complete
d0885b1c4ad3: Pull complete
e8a28cba5272: Pull complete
1c73c5610a41: Pull complete
6eca65574418: Pull complete
Digest: sha256:6dec7a2dcdcab55f61e0d9f8ed39b3e0b14478d64fa689f730f14a1ba500b1e7
Status: Downloaded newer image for wordpress:latest
5a3bb398686ba26de272f198954fbf7c58e1de5d0251ee598fb0def3f568d16e

C:\projects\wordpress-docker\My-Cheese-Blog>docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED              STATUS              PORTS                                                  NAMES
5a3bb398686b   wordpress   "docker-entrypoint.s…"   19 seconds ago       Up 9 seconds        0.0.0.0:8080->80/tcp, :::8080->80/tcp                  cheese_web
57aec3d69c9c   mysql:5.7   "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   cheese_db

C:\projects\wordpress-docker\My-Cheese-Blog>

As above, the WordPress image will be downloaded on first use and a container started from it.

Also, if the content directory is empty or doesn’t have a recognisable copy of WordPress installed (it looks for ./index.php or ./wp-includes/version.php it will install a copy from another path inside the container (/usr/src/wordpress/) and initialises it with the provided database details in ./wp-config.php.

Again, it may take a while for the WordPress files to be added.

When it’s ready, I recommend taking a peek at the files now in the ./html folder in the project and especially how wp-config.php uses the environment variables where provided.

Also, if you now look in the database you will see around 12 WordPress tables prefixed with cheese_ and containing the initial site content and settings.

Don’t stop or purge just yet – we’re going to continue with the WordPress installation next.

Install the WordPress Site

The database is ready, the web server is running and now it’s time to run the famous five-minute WordPress installation process.

Note: if you have any problems at this point or earlier, jump to the Troubleshooting section, below.

Navigate to http://localhost:8080 and when it’s ready you should see the following page:

WordPress installer language selection page
WordPress language selection

Select your language and click continue:

The famous five-minute WordPress installer step 1 - Information needed. Enter the site title, a username and password and email address.
The famous five-minute WordPress installation process step 1 – Information needed

The screenshot I’ve named this WordPress instance as “My Cheese Blog” with a simple user name and password (admin/cheesy) rather than accept the randomly generated one. 

Note: It gets tedious having to record the password every time when it’s only a development copy!  I’ve confirmed that it’s a weak password – when it’s published on the internet, I’ll use a stronger password.  Honest.

I’ve also provided my email address (it complains if you don’t) and I usually discourage search engines… not that they should be able to see it!

Clicking Install WordPress will create and initialise the database tables – which takes a short while – then presents you with the following screen:

The famous five-minute WordPress installer step 2 page – success.
The famous five-minute WordPress installation process step 2 – Success!

You can now click Log In using the details entered in step 1 (in the above example, username admin and password cheesy) and you will arrive at the WordPress Dashboard:

The WordPress Dashboard on first login after installation.
WordPress Dashboard on first login after installation

You can now configure this development WordPress as you please.

Before you do that, you might want to have a quick look at what the blog looks like with the default Theme (it’s Twenty Twenty-One in this version) so click the blog name (My Cheese Blog) in the top left corner:

The My Cheese Blog home page after initial installation with the Twenty-Twenty-One theme.
WordPress default home page for Twenty-Twenty-One theme

Hopefully, you now have a working WordPress installation on your development PC.  You can view and edit web site files in the .\html folder or use the web interface to modify settings, write content or work with plugins and themes.

If you stop and restart the containers (or even purge and create them again), the content and configuration will still be there and the containers should start much quicker.

Troubleshooting

Oh, dear.  It always seems to happen – you follow all the instructions to the letter but it still doesn’ work!

I’ll confess that I had a few problems while writing this, so here are some of the problems I encountered with suggested fixes.

If you still have trouble, leave a comment and I or another reader may be able to help.

When trying to connect to the database, you may see some variation of the following error (e.g., from a Java based client such as JetBrains IntelliJ IDEA):

java.net.ConnectException: Connection refused: connect. (7 sec, 322 ms)
[08S01]
	Communications link failure

	The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.

Similarly, with the WordPress container, I saw the following error when I first accessed the web site:

A web page showing the WordPress message “Error establishing a database connection”.
WordPress database error

These were usually caused by one of the following:

  • The database wasn’t ready for connections – my laptop is getting a bit old so I had to wait a while for the “Ready” message in the logs.
  • I forgot to publish the dB port – 3306.
  • Later versions of MySQL require you to use 127.0.0.1 as the hostname instead of localhost.
  • I used the default network so hostname resolution wasn’t working.

If you struggle with connecting the host folder you may have one of the following issues:

One option is to purge the network and containers, then also delete the data volume (if it got that far).  After that, check the scripts and try again.

Finally, after downloading and deleting the MySQL container many times while I captured the output, eventually I managed to corrupt the image somehow (the message “too many levels of symbolic links” appeared).  The only fix I found was to uninstall and re-install Docker Desktop.  Drastic, but this shouldn’t happen to you.

A Few Extra Features

I’ve added a few extra features to the GitHub version – including adding comments to the script files.

Externalise Configuration

So far, the scripts include user and password details for the database.  Ideally, we’d like to keep those private.  We can extract the values to separate files and call those in the scripts.

I moved the environment variables to env.bat (Windows) and .env (Mac or Linux) files and listed them in a .gitignore file to ensure they won’t be added to source control.

Note: Docker Compose also looks for a .env file (both the Windows and Mac or Linux versions).

I then created sample versions of them with blank values to store in GitHub.  You can copy the sample files and add your own values for the variables.

I updated the create scripts to run them at the start.

While adding the .gitignore file, I’ve also excluded the IDE files/folders (as I said earlier, I’m using JetBrains IntelliJ IDEA).

You may also consider excluding the html content folder if you wish to store the WordPress site or selected theme or plugin folders in source control separately.

Database Admin Containers

I’ve added two additional containers to the scripts to provide web based MySQL database admin features.

Adminer is mentioned on the Docker Hub pages for MySQL.  Once started I’ve mapped it to port 8081 and can be accessed at http://localhost:8081.

Secondly, phpMyAdmin is another popular choice and I’ve added configuration parameters to ensure it logs into the WordPress database automatically, and mapped it to port 8082.  It can be accessed at http://localhost:8082.

Quick Links

If you’re only working on one site then it’s simple to add bookmarks for the WordPress home page and admin pages.  However, if you add additional containers or work on other projects, your bookmarks can get cluttered.

I’ve added an “index.html” file for quick access to useful URLs for the project – WordPress home and admin, as well as the two dB admin containers.  You might want to add links to pages you visit often in the project or the live site.

Finally

I hope this has helped you get started with WordPress development using Docker CLI.

Add a comment if it has, or if you have any problems.

Leave a Reply

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

*