We’re continuing our examination of Docker data storage options. If you’d just like to dive into the code for adding storage volumes to the WordPress app from the Part 3, you can skip ahead to Part 4d – Working with Data Storage in Docker Compose.
Introduction
This is Part 4b in a series describing a project to create a local WordPress development environment using Docker-Compose. The series is structured as follows:
- Part 1 – Series introduction and creating a simple WordPress app
- Part 2 – Adding a database management tool
- Part 3 – Adding a web server
- Part 4a – Exploring anonymous volumes
- Part 4b – Exploring bind mounts
- Part 4c – Exploring named volumes
- Part 4d – Working with data storage
- Part 5 – Securing passwords with Docker secrets
- Part 6 – Securing network communications with self-signed certificates
- Part 7a – Creating your own certificate authority
- Part 7b – Securing network communications with your own certificate authority
- Part 8 – Hosting multiple WordPress sites on a single database server
- Part 9a – Installing and configuring a Bitwarden password manager
- Part 9b – Hosting Bitwarden behind a reverse proxy server
So far in this tutorial series we haven’t explicitly specified any data storage options for our WordPress app. As we saw in Part 4a however, Docker has managed our data, but has not persisted it from session to session. In Part 4b, we’ll continue discussing data storage within Docker, focusing on the bind mount option for persistent storage. We’ll also look at how to override or add specifications to our Compose file with a docker-compose.override.yml file.
If you need an overview of Docker data storage options before we move on you can read Docker’s storage overview. For the remainder of this post we will continue working with our project from Part 4a.
Exploring Bind Mounts with our WordPress App
We’ve already seen the use of bind mounts in Part 3 when we bound our nginx configuration directory to the nginx container’s internal configuration directory. The purpose in that case was to provide a common file system location where both we and the nginx container could access the nginx configuration file.
Likewise, we can add bind mounts to the MariaDB and WordPress containers to allow common access and persistence to these services’ data files. Use of a bind mount allows us to specify the file system location of the data files, persist our data from session to session, and even access those files when our app is shutdown.
Let’s explore bind mounts further by adding one to the MariaDB and WordPress services in our WordPress app. Configuring the bind mounts for a service in our Compose file only requires specifying a directory location on our host file system and the container directory, in the host:container format for the service under a volumes sub-topic key.
Let’s use a directory named db for the MariaDB service and one named wp for the WordPress service. We don’t need to create the directories within our project folder. Compose will create them for us if they don’t already exist when we start our app. The container directories we’ll bind to here are the same ones we examine in Part 4a in the anonymous volumes, /var/lib/mysql for the MariaDB service and /var/www/html for the WordPress service. Thus, the code we need to add to the MariaDB service is
volumes:
- ./db:/var/lib/mysql
and to the WordPress service is
volumes:
- ./wp:/var/www/html
Note that Compose will not alter a mount directory or its contents if it already exists at startup. However, the directory’s contents could be changed depending on the image being used. For example, the WordPress image will download the WordPress files when its container starts if certain files are not present. This could result in you losing some changes you’ve made to your WordPress installation if it was altered or damaged such that those files were deleted. It’s best to follow the typical practice of not altering the core WordPress files so you won’t lose any changes you make if WordPress updates itself.
While we could simply add this code to our Compose file, let’s use a Compose feature that lets us add or override Compose file configuration at startup with a Compose override file named docker-compose.override.yml. This is a bit of overkill for our purposes here, but it provides a nice example of this capability. You’ll likely find it useful in your own projects and in fact we’ll be using it again in Part 9a – Installing a Bitwarden Password Manager.
Overriding Compose File Configurations on Startup
The docker-compose.override.yml file allows us to add or override the service configurations specified in our docker-compose.yml file at startup. If Compose finds a docker-compose.override.yml file present in the startup directory it will combine it with the docker-compose.yml file in effect creating a project from both files. In general, items specified in the override file will either add to or override corresponding items in the main Compose file. See Share Compose Configurations for more detail and use cases.
For our simple case of adding bind mounts to our MariaDB and WordPress services, the docker-compose.override.yml file will be
docker-compose.override.yml
version: '3.7'
services:
db:
volumes:
- ./db:/var/lib/mysql
wp:
volumes:
- ./wp:/var/www/html
Note that the Compose version number must be the same in the docker-compose and override files. Create this file in your project directory and start up the WordPress app with docker-compose up -d (make sure you’ve shut your project down from part 4a first).
Compose will create the db and wp directories within our project folder and fill them with files similar to those in the anonymous volumes we examined in Part 4a. You can verify this for yourself by examining those directories.
contents of /part-4/db
total 141096
-rw-rw---- 1 999 999 18628608 Apr 29 11:19 aria_log.00000001
-rw-rw---- 1 999 999 52 Apr 29 11:19 aria_log_control
-rw-rw---- 1 999 999 976 Apr 29 11:19 ib_buffer_pool
-rw-rw---- 1 999 999 12582912 Apr 29 11:19 ibdata1
-rw-rw---- 1 999 999 50331648 Apr 29 11:19 ib_logfile0
-rw-rw---- 1 999 999 50331648 Apr 29 11:16 ib_logfile1
-rw-rw---- 1 999 999 12582912 Apr 29 11:19 ibtmp1
-rw-rw---- 1 999 999 0 Apr 29 11:16 multi-master.info
drwx------ 2 999 999 4096 Apr 29 11:19 mysql
drwx------ 2 999 999 4096 Apr 29 11:16 performance_schema
drwx------ 2 999 999 4096 Apr 29 11:19 wordpress
contents of /part-4/wp
total 212
-rw-r--r-- 1 www-data www-data 420 Nov 30 2017 index.php
-rw-r--r-- 1 www-data www-data 19935 Jan 1 2019 license.txt
-rw-r--r-- 1 www-data www-data 7368 Sep 2 2019 readme.html
-rw-r--r-- 1 www-data www-data 6939 Sep 2 2019 wp-activate.php
drwxr-xr-x 9 www-data www-data 4096 Dec 18 14:16 wp-admin
-rw-r--r-- 1 www-data www-data 369 Nov 30 2017 wp-blog-header.php
-rw-r--r-- 1 www-data www-data 2283 Jan 20 2019 wp-comments-post.php
-rw-r--r-- 1 www-data www-data 3180 Apr 29 11:16 wp-config.php
-rw-r--r-- 1 www-data www-data 2808 Apr 29 11:16 wp-config-sample.php
drwxr-xr-x 4 www-data www-data 4096 Dec 18 14:16 wp-content
-rw-r--r-- 1 www-data www-data 3955 Oct 10 2019 wp-cron.php
drwxr-xr-x 20 www-data www-data 12288 Dec 18 14:16 wp-includes
-rw-r--r-- 1 www-data www-data 2504 Sep 2 2019 wp-links-opml.php
-rw-r--r-- 1 www-data www-data 3326 Sep 2 2019 wp-load.php
-rw-r--r-- 1 www-data www-data 47597 Dec 9 05:30 wp-login.php
-rw-r--r-- 1 www-data www-data 8483 Sep 2 2019 wp-mail.php
-rw-r--r-- 1 www-data www-data 19120 Oct 15 2019 wp-settings.php
-rw-r--r-- 1 www-data www-data 31112 Sep 2 2019 wp-signup.php
-rw-r--r-- 1 www-data www-data 4764 Nov 30 2017 wp-trackback.php
-rw-r--r-- 1 www-data www-data 3150 Jul 1 2019 xmlrpc.php
You might also want to use the docker volume ls command to verify that Compose hasn’t created anonymous volumes for us as it did in Part 4a. Compose did not need to create anonymous volumes as we provided specific mounting points for the containers’ internal volumes in our Compose override file.
You may also want to test that your data persists between sessions. You can do this as follows:
- Once your app is started, navigate to your app in your browser with http://<host name or IP address>.
- Complete the WordPress installation and log into your WordPress site.
- Make some changes to your site, such as changing the theme, site title or perhaps some setting. Don’t forget to save the settings changes with the Save button at the bottom of the page if you make changes in the Settings tab.
- Close your browser page and shutdown your app with docker-compose down. Note that it doesn’t matter if you use the -v option here or not because we haven’t added any named volumes to our services.
- Restart your app with docker-compose up -d and navigate to it once again in your browser. If you changed your theme or site title the change should be readily apparent. If you made a change to certain settings, you may need to log in to view the change. I find the easiest way to do this is to request the site’s administrative page with http://<host name or IP>/wp-admin. This will take you to the same login page you used to log in to your site after the WordPress installation step.
We’ve seen above that changes we make to our app persist between sessions. Let’s quickly demonstrate that changes we make to our data files are reflected in both our local data and internal container directories.
Revisiting Our Containers with the Docker Inspect and Exec Commands
As in Part 4a, let’s examine the contents of our WordPress app containers again with the docker inspect and docker exec commands. With the docker inspect part-4_db_1 and docker inspect part-4_wp_1 commands you should find about midway down the output Mounts sections similar to the following.
"Mounts": [
{
"Type": "bind",
"Source": "/home/blog/Documents/part-4/db",
"Destination": "/var/lib/mysql",
"Mode": "rw",
"RW": true,
"Propagation": "rprivate"
}
],
"Mounts": [
{
"Type": "bind",
"Source": "/home/blog/Documents/part-4/wp",
"Destination": "/var/www/html",
"Mode": "rw",
"RW": true,
"Propagation": "rprivate"
}
],
Here you can see that as we specified in our Compose file, our /part-4/db directory has been mounted in the MariaDB container’s /var/lib/mysql directory. Also, the /part-4/wp directory has been mounted in the WordPress container’s /var/www/html directory.
Let’s continue examining our WordPress app containers with the docker exec command. You’ll recall from Part 4a that the docker exec command allows us to run commands within a container. Let’s run a bash shell within our MariaDB and WordPress containers to examine our mounted directories. Let’s start with the MariaDB container with the following command.
sudo docker exec -it part-4_db_1 /bin/bash
Once inside the MariaDB container inspect the contents of the /var/lib/mqsql directory as follows.
root@edf505160d3c:/# cd var/lib/mysql
root@edf505160d3c:/var/lib/mysql# ls -l
total 141096
-rw-rw---- 1 mysql mysql 18628608 Apr 29 19:04 aria_log.00000001
-rw-rw---- 1 mysql mysql 52 Apr 29 19:04 aria_log_control
-rw-rw---- 1 mysql mysql 2289 Apr 29 19:04 ib_buffer_pool
-rw-rw---- 1 mysql mysql 50331648 Apr 29 19:06 ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Apr 29 18:16 ib_logfile1
-rw-rw---- 1 mysql mysql 12582912 Apr 29 19:04 ibdata1
-rw-rw---- 1 mysql mysql 12582912 Apr 29 19:06 ibtmp1
-rw-rw---- 1 mysql mysql 0 Apr 29 18:16 multi-master.info
drwx------ 2 mysql mysql 4096 Apr 29 18:19 mysql
drwx------ 2 mysql mysql 4096 Apr 29 18:16 performance_schema
drwx------ 2 mysql mysql 4096 Apr 29 18:59 wordpress
You’ll see that the files are the same as those in our local file system that we viewed above, though the timestamps may be different if you’ve made any changes since then (and as we saw in Part 4a, the time zone internal to the container may be different from our local time zone). Exit out of the MariaDB container with the exit command and start a bash shell in the WordPress container with the following command.
sudo docker exec -it part-4_wp_1 /bin/bash
Now inspect the contents of the /var/www/html directory as follows.
root@12901e269dd6:/var/www/html# ls -l
total 212
-rw-r--r-- 1 www-data www-data 420 Nov 30 2017 index.php
-rw-r--r-- 1 www-data www-data 19935 Jan 1 2019 license.txt
-rw-r--r-- 1 www-data www-data 7368 Sep 2 2019 readme.html
-rw-r--r-- 1 www-data www-data 6939 Sep 3 2019 wp-activate.php
drwxr-xr-x 9 www-data www-data 4096 Dec 18 22:16 wp-admin
-rw-r--r-- 1 www-data www-data 369 Nov 30 2017 wp-blog-header.php
-rw-r--r-- 1 www-data www-data 2283 Jan 21 2019 wp-comments-post.php
-rw-r--r-- 1 www-data www-data 2808 Apr 29 19:06 wp-config-sample.php
-rw-r--r-- 1 www-data www-data 3180 Apr 29 19:06 wp-config.php
drwxr-xr-x 5 www-data www-data 4096 Apr 29 19:00 wp-content
-rw-r--r-- 1 www-data www-data 3955 Oct 10 2019 wp-cron.php
drwxr-xr-x 20 www-data www-data 12288 Dec 18 22:16 wp-includes
-rw-r--r-- 1 www-data www-data 2504 Sep 3 2019 wp-links-opml.php
-rw-r--r-- 1 www-data www-data 3326 Sep 3 2019 wp-load.php
-rw-r--r-- 1 www-data www-data 47597 Dec 9 13:30 wp-login.php
-rw-r--r-- 1 www-data www-data 8483 Sep 3 2019 wp-mail.php
-rw-r--r-- 1 www-data www-data 19120 Oct 15 2019 wp-settings.php
-rw-r--r-- 1 www-data www-data 31112 Sep 3 2019 wp-signup.php
-rw-r--r-- 1 www-data www-data 4764 Nov 30 2017 wp-trackback.php
-rw-r--r-- 1 www-data www-data 3150 Jul 1 2019 xmlrpc.php
Once again, we see the same WordPress data files as above, as expected since we’re just inspecting the mounted volume.
Lastly, let’s try an experiment to prove that we’re looking at files in our local directory. Create a new file, tmp.txt, within the WordPress container /var/www/html directory. You can do this as follows.
root@12901e269dd6:/var/www/html# touch tmp.txt
root@12901e269dd6:/var/www/html# ls -l tmp.txt
-rw-r--r-- 1 root root 0 Apr 29 20:34 tmp.txt
Looking at the /var/www/html directory from within the container, you can clearly see the addition of the tmp.txt file. Now exit the container with the exit command and examine the contents of your wp directory. You’ll notice that the tmp.txt file is in this directory as well (again with a possible time zone difference).
-rw-r--r-- 1 root root 0 Apr 29 13:34 tmp.txt
In fact, it is the exact file we created from within the WordPress container. You can make changes to or delete the file here and the changes will be reflected within the container. Similarly, you can create files here and they will be reflected within the container. This demonstrates one of the attractive features of the bind mount storage option.
Wrapping Up
That enough of bind mounts for now. They provide a way to persist your data while maintaining easy access to it. Their main drawback is that they require a defined file structure on the host system. For our simple app this wasn’t a problem, but it may be for more complex apps or ones distributed across diverse systems. To overcome this limitation and to make data readily accessible between services, Docker recommends the use of named volumes. We’ll discuss these next in Part 4c – Exploring Named Volumes. To get ready, shutdown your project with docker-compose down when you are finished with it.