Docker and docker-compose for WordPress testing - 01
June 2, 2020
The Docker promise, in a WordPress developer context
Docker is almost everywhere now, and there are many good reasons for it.
I'm not going into all the reasons that make Docker amazing. Instead, I would focus on the possibilities it offers me, as a WordPress developer sharing production and test code across different teams and operating systems.
"It works on my machine" might be a meme, but it's still frequent feedback I run into when reviewing pull requests with broken tests that are, invariably, passing locally and failing in CI. Supposedly.
It would be great for a team, or even just for a developer using multiple operating systems, to have identical testing environments running on each developer machine, whatever the operating system.
The purpose of this series is to consolidate the knowledge I got along the way of learning "how to docker" into a progressive series of articles moving past base, copy-and-paste examples. Ideally, to set up to an OS-independent, reusable, flexible docker-compose based, WordPress local, and CI (Continuous Integration) testing and development environment.
Up and done
I'm using, to start, a container I use daily: the official WordPress container from Dockerhub.
This container is a constant in my builds and testing environments. I've spent quite a bit of time learning it's ins-and-outs and collecting several "gotchas along the path.
I've created a minimal docker-compose.yml
configuration file copying the example from the dockerhub official WordPress image documentation:
cd ~/Repos
mkdir wp-docker
cd wp-docker
touch docker-compose.yml
I've just moved around some lines but changed nothing.
version: '3.1'
volumes:
wordpress:
db:
services:
wordpress:
image: wordpress
restart: always
ports:
# Expose port 80 of the container on port 8080 of localhost.
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- wordpress:/var/www/html
db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql
I run the command to start the stack and wait for the setup to be ready:
docker-compose up
If the images are not already present on my machine, they will be downloaded and extracted first, and this will happen only once the first time I require new images.
After a bit of setup and build processes, I will be left with a terminal tab or window following (tailing) the two containers log files:
db_1 | 2020-05-16T11:24:01.540475Z 0 [Note] Event Scheduler: Loaded 0 events
db_1 | 2020-05-16T11:24:01.540824Z 0 [Note] mysqld: ready for connections.
db_1 | Version: '5.7.29' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
wordpress_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 192.168.80.3. Set the 'ServerName' directive globally to suppress this message
wordpress_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 192.168.80.3. Set the 'ServerName' directive globally to suppress this message
wordpress_1 | [Sat May 16 11:24:03.714651 2020] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.38 (Debian) PHP/7.3.16 configured -- resuming normal operations
wordpress_1 | [Sat May 16 11:24:03.714774 2020] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
In the docker-compose.yml
file I've specified, in services.wordpress.ports
section, that I would like to expose port 80
of the container on port 8080
of localhost.
Up and down and up again
When I open the http://localhost:8080
address in my browser, though, WordPress will not be installed.
I go through the installation and will end up with a working WordPress installation:
If I stop the WordPress and database server closing the terminal tab running the logs or sending it the termination command (Ctrl+c
), then the site will not be available anymore at http://localhost:8080
, as expected.
How would I restart the WordPress installation to work on it, though? Using the same command I've used before:
docker-compose up
Do I need to install WordPress again? No.
In the docker-compose.yml
file I've defined two volumes
: wordpress
and db
.
The wordpress
volume will store, in my host machine filesystem, the WordPress container files, the content of the WordPress installation.
The db
volume will store the data produced by the database instance used by WordPress, again in my host machine filestystem.
The concept of volumes
and "Docker volumes" was tricky for me to grasp until I've understood that it just means "store what files you create or update during your work on my machine".
So where are the files?
If they are on my machine, then I would expect the files to be somewhere I can see them; in the same directory containing the docker-compose.yml
file, ideally.
Yet they are not there, the directory contains only, even while the wordpress
and db
containers are running, the docker-compose.yml
file:
Repos/wp-docker » tree -L 1 .
.
└── docker-compose.yml
0 directories, 1 file
Getting the path to the real location where, in the host filesystem, the files live requires a bit of inspection into how Docker stores container information.
First, I list the currently defined WordPress containers with docker volume ls
:
Repos/wp-docker » docker volume ls
DRIVER VOLUME NAME
local wp-docker_db
local wp-docker_wordpress
As expected, Docker created two volumes have named after the directory where the docker-compose.yml
file lives:
wp-docker_wordpress
for thewordpress
volumewp-docker_db
for thedb
volume
Since I want to see the WordPress container files, the container I'm interested in is the wp-docker_wordpress
one.
The command docker volume inspect wp-docker_wordpress
will provide me with useful information about the volume:
Repos/wp-docker » docker volume inspect wp-docker_wordpress
[
{
"CreatedAt": "2020-05-16T11:46:15Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "wp-docker",
"com.docker.compose.version": "1.25.5",
"com.docker.compose.volume": "wordpress"
},
"Mountpoint": "/var/lib/docker/volumes/wp-docker_wordpress/_data",
"Name": "wp-docker_wordpress",
"Options": null,
"Scope": "local"
}
]
So, are the wordpress
volume files, used by the wordpress
container, from the wp-docker
project, in the /var/lib/docker/volumes/wp-docker_wordpress/_data
directory?
Turns out no, they are not:
Repos/wp-docker » ls -la /var/lib/docker/volumes/wp-docker_wordpress/_data
ls: /var/lib/docker/volumes/wp-docker_wordpress/_data: No such file or directory
I could go into the rabbit hole of trying and finding them, but Docker and docker-compose
provide a structured and documented way to store the container data exactly where I need it.
Binding volumes
Binding volumes is container jargon to say:
When I put stuff in this directory on my machine, it should appear in this directory in the container. Possibly the other way too.
I do want the WordPress installation files to exist, on my host machine, somewhere practical.
Specifically in the ~/Repos/wp-docker/_wordpress
directory.
I update the docker-compose.yml
file to reflect that.
version: '3.1'
volumes:
db:
services:
wordpress:
image: wordpress
restart: always
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
# Not using the volume defined in the volumes section anymore, so I've removed it.
# The `.` means "from the directory that contains this file", where "this file" is the docker-compose.yml file.
- ./_wordpress:/var/www/html
db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql
Two things to notice:
I've changed the services.wordpress.volumes
entry to ./_wordpress:/var/www/html
.
The .
in the ./_wordpress
first fragment of the wordpress
service volume definition means "from the directory that contains this file", where "this file" is the docker-compose.yml
file.
In docker-compose.yml
files, as in the -v
parameter of the docker
CLI API, volume bindings are read on_the_host:on_the_container
. The same applies to port bindings.
The first, the host
part of a volume binding can either be a path relative to the docker-compose.yml
directory, or an absolute path. I'm using the first option here to make the implementation portable.
Since I will not be using the wordpress
volume anymore, I've removed it from the file.
If all goes well, firing up the stack again should create a _wordpress
directory beside the docker-compose.yml
file and put WordPress root directory in there.
I see WordPress
I fire up the stack again and wait, looking at the logs, for the message from the WordPress container to confirm it's ready.
Repos/wp-docker » docker-compose up -d
I open a new terminal tab and check what's in the project directory:
Repos/wp-docker » tree -L 1 .
.
└── docker-compose.yml
0 directories, 1 file
Nothing happened. Why?
Well: Docker is an efficient worker and, since I ran the stack before, will "recycle", reuse, the containers I was running the last time.
And the wordpress
container I used before was never told to bind the /var/www/html
directory to the _wordpress
one in the project root directory. So it didn't.
How do I take control of this back? By tearing down the stack and firing it up again with the down
(short for "tear down") command:
Repos/wp-docker » docker-compose down
Stopping wp-docker_wordpress_1 ... done
Stopping wp-docker_db_1 ... done
Removing wp-docker_wordpress_1 ... done
Removing wp-docker_db_1 ... done
Removing network wp-docker_default
Repos/wp-docker » docker-compose up
Creating network "wp-docker_default" with the default driver
Creating wp-docker_wordpress_1 ... done
Creating wp-docker_db_1 ... done
Attaching to wp-docker_db_1, wp-docker_wordpress_1
db_1 | 2020-05-17 11:16:48+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.29-1debian9 started.
db_1 | 2020-05-17 11:16:49+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
db_1 | 2020-05-17 11:16:49+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.29-1debian9 started.
db_1 | 2020-05-17T11:16:49.337125Z 0 [Warning
...
db_1 | Version: '5.7.29' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
wordpress_1 | WordPress not found in /var/www/html - copying now...
wordpress_1 | Complete! WordPress has been successfully copied to /var/www/html
wordpress_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 192.168.96.3. Set the 'ServerName' directive globally to suppress this message
wordpress_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 192.168.96.3. Set the 'ServerName' directive globally to suppress this message
wordpress_1 | [Sun May 17 11:16:59.711493 2020] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.38 (Debian) PHP/7.3.16 configured -- resuming normal operations
wordpress_1 | [Sun May 17 11:16:59.711569 2020] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
Can I see WordPress files in the _wordpress
directory now?!
Repos/wp-docker » tree -L 2 .
.
├── _wordpress
│ ├── index.php
│ ├── license.txt
│ ├── readme.html
│ ├── wp-activate.php
│ ├── wp-admin
│ ├── wp-blog-header.php
│ ├── wp-comments-post.php
│ ├── wp-config-sample.php
│ ├── wp-config.php
│ ├── wp-content
│ ├── wp-cron.php
│ ├── wp-includes
│ ├── wp-links-opml.php
│ ├── wp-load.php
│ ├── wp-login.php
│ ├── wp-mail.php
│ ├── wp-settings.php
│ ├── wp-signup.php
│ ├── wp-trackback.php
│ └── xmlrpc.php
└── docker-compose.yml
4 directories, 18 files
I got myself a WordPress copy, ready to run, and in my host machine file system.
Trusting no one, I want to see if doing something on the host would modify the files on the container and vice-versa.
Two-way binding test
The first test will be to delete the "Hello Dolly" plugin from the host machine and see if the "Hello Dolly" plugin does disappear in the container:
rm ./_wordpress/wp-content/plugins/hello.php
A check-in the WordPress installation, at http://localhost:8080/wp-admin/plugins.php
confirms my expectation:
To test that changing something in the container would, in turn, change it on the guest I will try to install the Hello Dolly
plugin from the WordPress plugin repository again; if all goes as intended, then it should re-appear on the host.
Repos/wp-docker » tree -L 1 ./_wordpress/wp-content/plugins
./_wordpress/wp-content/plugins
├── akismet
├── hello-dolly
└── index.php
2 directories, 1 file
And it does.
This confirms volume binding works as intended.
On macOS. Will it be the same on Linux? And Windows?
Spoiler: no, it requires some leg work and understanding of file modes that I will plunge into in my next post.