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.
We are going to use the following two official Docker images:
- MySQL – where WordPress will store content and settings, and
- 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:
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.
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:
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
orwp_user
- Password: “
verySecret
” forroot
, or “Camembert
” forwp_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.
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:
Select your language and click continue:
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:
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:
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:
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:
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 oflocalhost
. - 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:
- Use the full path to the folder (e.g.
C:\projects\wordpress-docker\My-Cheese-Blog
rather than./html
). - Make sure Docker has access to your host folders – see Unblock Docker for Windows Firewall Issues with Host Volumes.
- Have a look at How to install WordPress.
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.