Exploring Bind Mounts in Docker Compose

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:

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.