Where’s My Data! Exploring Anonymous Volumes in Docker Compose

We’re going to get a bit technical in this part of the series. If you’d just like to dive into the code for adding storage volumes to the WordPress app from Part 3, you can skip ahead to Part 4d – Working with Data Storage in Docker Compose.

Introduction

This is Part 4a in a series describing a project to create a local WordPress development environment using Docker-Compose. The series is structured as follows:

In Part 4, we’ll be discussing data storage within Docker in some detail. Because there is a lot to cover, I’ve broken Part 4 into four posts. In this post we’ll look at how Docker has stored the data in our simple WordPress app so far. We’ll then move on to specific Docker data storage options in the next two posts. And finally, in the final post of Part 4 we’ll incorporate data storage into our WordPress app. Along the way, we’ll also look more in-depth at various Docker objects with the Docker volume, inspect and exec commands. We’ll also looked briefly at some Docker image source code to see how the anonymous volumes associated with our project were created.

That’s a lot to cover, so let’s jump right in with an overview of Docker data storage options.

How Docker Manages Data

By default, Docker does not maintain a container’s data after the container has been shut down. To persist data beyond your container life you must specify one of Docker’s two methods of persisting data, the bind mount or volume, in the service configuration in a project’s Compose file. Bind mounts mount a host file or directory into the container. Volumes are created and managed by Docker in its own storage area and can either be named or not. Docker has a useful overview discussing these storage options that you can review for more information. Note that data persistence may be baked into a service image so technically you might not have to specify a data storage method yourself for data to be persistent from run to run.

The bind mount and volume, both use the volumes key to configure storage for a service in the Compose file. We used the volumes key with a bind mount in Part 3 when we mounted the ./nginx directory with our nginx configuration file to the nginx container’s internal configuration directory, /etc/nginx/conf.d.

Docker volumes were also in use behind the scenes in Parts 1-3 though you didn’t realize it or specify it in the Compose file. This is because the creators of the MariaDB and WordPress images specified an unnamed volume for their services. In this way, the data from our simple WordPress app was actually preserved, though being anonymous, it wasn’t restored if the app was restarted. You could use some of the techniques discussed throughout Part 4 to restore the data in these anonymous volumes to their appropriate containers. But there is a better way, specifying bind mounts and named volumes in the Compose file. We’ll look at bind mounts and named volumes in more detail in Parts 4b and 4c. But first, let’s look at the anonymous volumes that have been storing our data since Part 1 of this series.

Examining Docker Objects with the Inspect Command

For the remainder of this post we will be examining Docker objects in some detail. We will use our project from Part 3 in our examination. You can either use your Part 3 project as is or make a copy of its directory for use again here. I’ve done the latter, creating a new directory and naming it part-4. Once you have your project directory prepared, start up your project with the docker-compose up -d command from within its directory.

Once your WordPress app is up and running, we can examine its Docker objects with various Docker commands. We’ll only cover a few of the possibilities here, but you can see them all at docker child commands (docker –help will also provide a list of available child commands). Docker in fact provides many commands for working with containers. Docker-Compose provides some as well. You may find it helpful to review their reference pages at Docker Command Reference and Docker-Compose Command Reference to understand the breadth of commands available.

We’ve already seen how to obtain a list of our running containers with the docker ps command which will list something like the following for our current project.

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
9ed198c8dd48        wordpress           "docker-entrypoint.s…"   12 minutes ago      Up 12 minutes       80/tcp               part-4_wp_1
1ee61dcd51b8        nginx               "nginx -g 'daemon of…"   12 minutes ago      Up 12 minutes       0.0.0.0:80->80/tcp   part-4_nginx_1
2bcc78e7f830        adminer             "entrypoint.sh docke…"   12 minutes ago      Up 12 minutes       8080/tcp             part-4_adminer_1
da0d859dc5d3        mariadb             "docker-entrypoint.s…"   12 minutes ago      Up 12 minutes       3306/tcp             part-4_db_1

You can see from this that all four of our project’s services are up and running. See Part 2 – Adding a Database Management Tool if you need a refresher on what’s shown above.

The docker volume command allows us to work with Docker volumes. For example, the docker volume ls command will list the Docker volumes on your system. It will produce something like the following for our project.

DRIVER              VOLUME NAME
local               2c92849bd78f94470dbc76485fba03a2dd0fc8b9fd842dd9a45d9a819cfaffbd
local               854b9834bab2b3cb64b35932deee872d84a0d760e294791049e929bf483e15aa

This shows that our WordPress app has two volumes, named with long strings of seemingly random numbers and letters. These are anonymous volumes that Docker has created to support our apps’ services. Which services you might ask. Let’s find out using the docker inspect command. Note that if your list above includes more than two anonymous volumes then you either have other Docker projects running or you haven’t shut down previous projects with the remove volume option, docker-compose down -v, as discussed at the end of Part 1.

The docker inspect command displays information about various Docker objects, such as containers, volumes, images, and networks. Let’s use the docker inspect command on our anonymous volumes shown above. Note: replace the long string in the commands below with one of the strings returned from your docker volume ls command.

docker inspect 854b9834bab2b3cb64b35932deee872d84a0d760e294791049e929bf483e15aa

will display something like

[
    {
        "CreatedAt": "2020-01-23T08:42:56-08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/854b9834bab2b3cb64b35932deee872d84a0d760e294791049e929bf483e15aa/_data",
        "Name": "854b9834bab2b3cb64b35932deee872d84a0d760e294791049e929bf483e15aa",
        "Options": null,
        "Scope": "local"
    }
]

You’ll get something similar with

docker inspect 2c92849bd78f94470dbc76485fba03a2dd0fc8b9fd842dd9a45d9a819cfaffbd

Inspection of these anonymous volumes didn’t provide a lot of information, but we can see that they are mounted at /var/lib/docker/volumes, which is where Docker stores its volumes in the local file system. If you examine the contents of /var/lib/docker/volumes you’ll find something like the following.

total 52
drwxr-xr-x 3 root root  4096 Jan 23 08:42 2c92849bd78f94470dbc76485fba03a2dd0fc8b9fd842dd9a45d9a819cfaffbd
drwxr-xr-x 3 root root  4096 Jan 23 08:42 854b9834bab2b3cb64b35932deee872d84a0d760e294791049e929bf483e15aa
-rw------- 1 root root 65536 Jan 23 08:42 metadata.db

These directory names the same as the anonymous volumes above. Looking at the contents of each directory in turn we find the following.

total 122936
-rw-rw---- 1 999 999    32768 Jan 23 08:53 aria_log.00000001
-rw-rw---- 1 999 999       52 Jan 23 08:53 aria_log_control
-rw-rw---- 1 999 999     6176 Jan 23 08:53 ib_buffer_pool
-rw-rw---- 1 999 999 12582912 Jan 23 08:53 ibdata1
-rw-rw---- 1 999 999 50331648 Jan 23 08:53 ib_logfile0
-rw-rw---- 1 999 999 50331648 Jan 23 08:42 ib_logfile1
-rw-rw---- 1 999 999 12582912 Jan 23 08:53 ibtmp1
-rw-rw---- 1 999 999        0 Jan 23 08:42 multi-master.info
drwx------ 2 999 999     4096 Jan 23 08:53 mysql
drwx------ 2 999 999     4096 Jan 23 08:42 performance_schema
drwx------ 2 999 999     4096 Jan 23 08:53 wordpress

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 14:44 readme.html
-rw-r--r--  1 www-data www-data  6939 Sep  2 17:41 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 Jan 23 08:42 wp-config.php
-rw-r--r--  1 www-data www-data  2808 Jan 23 08:42 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 15:52 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 17:41 wp-links-opml.php
-rw-r--r--  1 www-data www-data  3326 Sep  2 17:41 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 17:41 wp-mail.php
-rw-r--r--  1 www-data www-data 19120 Oct 15 08:37 wp-settings.php
-rw-r--r--  1 www-data www-data 31112 Sep  2 17:41 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 you become familiar with MariaDB and WordPress files, it will be pretty clear that these anonymous volumes are associated with our MariaDB and WordPress services. We can confirm this by inspecting the MariaDB and WordPress containers. When used on a container, the docker inspect command will list details regarding the configuration of that container. For our purposes here, let’s examine the Mounts section for the MariaDB and WordPress containers as follows (note: replace part-4_db_1 and part-4_wp_1 in the commands below if your containers are named differently):

docker inspect part-4_db_1

will have a Mounts section, somewhat over halfway through the output list, something like

        "Mounts": [
            {
                "Type": "volume",
                "Name": "2c92849bd78f94470dbc76485fba03a2dd0fc8b9fd842dd9a45d9a819cfaffbd",
                "Source": "/var/lib/docker/volumes/2c92849bd78f94470dbc76485fba03a2dd0fc8b9fd842dd9a45d9a819cfaffbd/_data",
                "Destination": "/var/lib/mysql",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

and

docker inspect part-4_wp_1

will have a Mounts section something like

        "Mounts": [
            {
                "Type": "volume",
                "Name": "854b9834bab2b3cb64b35932deee872d84a0d760e294791049e929bf483e15aa",
                "Source": "/var/lib/docker/volumes/854b9834bab2b3cb64b35932deee872d84a0d760e294791049e929bf483e15aa/_data",
                "Destination": "/var/www/html",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

This information is very similar to what we saw when inspecting our two anonymous volumes, so it’s clear these volumes are associated with our MariaDB and WordPress services.

One more item of interest from our inspection of the MariaDB and WordPress containers is the Destination, /var/lib/mysql for the MariaDB container and /var/www/html for the WordPress container. These represent the internal directories in the container where the associated anonymous volumes are mounted. We’ll examine these internal directories more in the next section.

Examining Docker Objects with the Exec Command

We can further examine the contents of our containers with the docker exec command. The docker exec command allows us to run a command inside a container. I first learned about the possibility of working within a container from William Patton’s excellent article on Multiple WordPress Containers. William covers many uses of the docker exec command and we’ll use a few in future tutorials, but for now we’ll keep it simple.

We can inspect the internal structure of the container by running a bash shell inside it. Let’s try this with the MariaDB container with the following command (note: again, replace part-4_db_1 and part-4_wp_1 in the commands below if your containers are named differently):

sudo docker exec -it part-4_db_1 /bin/bash

This will start a bash shell within the db container. The -it options opens up an interactive terminal within the container allowing you to perform administrative tasks or run tests. You should see a bash prompt something like the following after executing the command above.

root@da0d859dc5d3:/#

This indicates we’ve started a terminal as a root user in the root directory of container da0d859dc5d3. From the docker ps command we know that this is the MariaDB container. Examining the contents of the directory with a ls -l command we get the following list.

total 72
drwxr-xr-x   1 root root 4096 Jan 16 04:37 bin
drwxr-xr-x   2 root root 4096 Apr 24  2018 boot
drwxr-xr-x   5 root root  340 Jan 23 16:42 dev
drwxr-xr-x   2 root root 4096 Jan 16 04:37 docker-entrypoint-initdb.d
lrwxrwxrwx   1 root root   34 Jan 16 04:37 docker-entrypoint.sh -> usr/local/bin/docker-entrypoint.sh
drwxr-xr-x   1 root root 4096 Jan 23 16:42 etc
drwxr-xr-x   2 root root 4096 Apr 24  2018 home
drwxr-xr-x   1 root root 4096 May 23  2017 lib
drwxr-xr-x   2 root root 4096 Jan 12 21:10 lib64
drwxr-xr-x   2 root root 4096 Jan 12 21:09 media
drwxr-xr-x   2 root root 4096 Jan 12 21:09 mnt
drwxr-xr-x   2 root root 4096 Jan 12 21:09 opt
dr-xr-xr-x 303 root root    0 Jan 23 16:42 proc
drwx------   1 root root 4096 Jan 16 04:37 root
drwxr-xr-x   1 root root 4096 Jan 16 04:37 run
drwxr-xr-x   1 root root 4096 Jan 16 04:37 sbin
drwxr-xr-x   2 root root 4096 Jan 12 21:09 srv
dr-xr-xr-x  13 root root    0 Jan 23 16:42 sys
drwxrwxrwt   1 root root 4096 Jan 23 16:53 tmp
drwxr-xr-x   1 root root 4096 Jan 12 21:09 usr
drwxr-xr-x   1 root root 4096 Jan 12 21:10 var

Change the directory to /var/lib/mysql with the following command.

cd /var/lib/mysql

Examining the contents of the directory again we get the following list

total 122936
-rw-rw---- 1 mysql mysql    32768 Jan 23 16:53 aria_log.00000001
-rw-rw---- 1 mysql mysql       52 Jan 23 16:53 aria_log_control
-rw-rw---- 1 mysql mysql     6176 Jan 23 16:53 ib_buffer_pool
-rw-rw---- 1 mysql mysql 50331648 Jan 23 16:53 ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Jan 23 16:42 ib_logfile1
-rw-rw---- 1 mysql mysql 12582912 Jan 23 16:53 ibdata1
-rw-rw---- 1 mysql mysql 12582912 Jan 23 16:53 ibtmp1
-rw-rw---- 1 mysql mysql        0 Jan 23 16:42 multi-master.info
drwx------ 2 mysql mysql     4096 Jan 23 16:53 mysql
drwx------ 2 mysql mysql     4096 Jan 23 16:42 performance_schema
drwx------ 2 mysql mysql     4096 Jan 23 16:53 wordpress

These are the same as the database files in the /var/lib/docker/volumes directory for the anonymous volume associated with the MariaDB container. Let’s exit from the container with the exit command. We’ll come back to it in Part 4b.

We can similarly examine the interior of the WordPress container and its associated anonymous volume with the following command.

sudo docker exec -it part-4_wp_1 /bin/bash

This gives us a prompt within the WordPress container something like the following.

root@9ed198c8dd48:/var/www/html#

You can see that we’re in the WordPress container’s /var/www/html directory. This is different than when we entered the MariaDB container which put us in the root directory. This is because the WordPress image sets /var/www/html as the working directory. You’ll recall this is also the mounting point for the anonymous volume associated with this container. Let’s look at the directory contents with the ls -l command.

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 21:44 readme.html
-rw-r--r--  1 www-data www-data  6939 Sep  3 00:41 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 Jan 23 16:42 wp-config-sample.php
-rw-r--r--  1 www-data www-data  3180 Jan 23 16:42 wp-config.php
drwxr-xr-x  4 www-data www-data  4096 Dec 18 22:16 wp-content
-rw-r--r--  1 www-data www-data  3955 Oct 10 22:52 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 00:41 wp-links-opml.php
-rw-r--r--  1 www-data www-data  3326 Sep  3 00:41 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 00:41 wp-mail.php
-rw-r--r--  1 www-data www-data 19120 Oct 15 15:37 wp-settings.php
-rw-r--r--  1 www-data www-data 31112 Sep  3 00:41 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

These are the same files as those in the /var/lib/docker/volumes directory for the anonymous volume associated with the WordPress container, though it is interesting to note that the time zone of the container is different than the host system. We’ll do more with the WordPress container in Part 4b but for now let’s leave it with the exit command.

We’ve seen that Docker has created two volumes for our WordPress app. But where did they come from since we didn’t specify them in our Compose file. To find out takes a bit of investigation into the image source code for the MariaDB and WordPress images.

Docker Image Source Code

The Docker images that we’ve been using for our project are all open-source with repositories maintained on GitHub. The GitHub repositories for our images are at MariaDB, WordPress, Adminer, and nginx. We’re specifically interested in where our anonymous volumes came from so it makes sense to look at the source for the MariaDB and WordPress images to continue our investigation.

A main piece of code for these services’ Docker images is a Dockerfile, a file that provides the instructions to build a Docker image. You’ll find several in each of the GitHub links above. The different Dockerfiles represent different versions of the images which may target different platforms or setups. All of the Dockerfiles for a given image will be pretty similar though. If you examine a Dockerfile for the MariaDB and WordPress images, you’ll see toward the end of the file that an unnamed volume is specified at /var/lib/mysql for the MariaDB image and that the WordPress image specifies one at /var/www/html.

Dockerfiles are routinely updated so providing a direct link isn’t useful, but you can navigate to the current Dockerfile by clicking the “latest” tag at the MariaDB and WordPress Docker Hub pages. Below are snippets from the latest MariaDB and WordPress Dockerfiles at the time of this writing.

MariaDB Dockerfile snippet

...
VOLUME /var/lib/mysql

COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s usr/local/bin/docker-entrypoint.sh / # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 3306
CMD ["mysqld"]

You can see here that the MariaDB Dockerfile specifies a volume mounted at /var/lib/mysql. Also interesting is that this is where the Dockerfile exposes port 3306 for communications as we saw with the docker ps command above.

WordPress Dockerfile snippet

...
VOLUME /var/www/html

ENV WORDPRESS_VERSION 5.4
ENV WORDPRESS_SHA1 d5f1e6d7cadd72c11d086a2e1ede0a72f23d993e
...

Here the WordPress Dockerfile specifies a volume mounted at /var/www/html. If you look through the Dockerfile you’ll likely be unable to find mention of a working directory or port being exposed, which we would expect given our investigations so far. However, at the top of the WordPress Dockerfile you’ll notice that the file incorporates another image. At the time of this writing the latest WordPress image incorporated PHP version 7.3.

FROM php:7.3-apache
...

Finding the relevant tag at PHP Tags will take you to the appropriate PHP Dockerfile. Towards the bottom of that file you’ll find the following snippet.

PHP Dockerfile snippet

...
WORKDIR /var/www/html

EXPOSE 80
CMD ["apache2-foreground"]
...

Here we see where the working directory /var/www/html is specified and port 80 is exposed for the WordPress image.

Since our Compose file doesn’t specify a named volume at the /var/lib/mysql or /var/www/html mounting points, Docker-Compose creates anonymous volumes for data storage.

Wrapping Up

We took a pretty deep dive into Docker internals, but we’ve really only scratched the surface. We looked at several Docker commands, volume, inspect, and exec, that can be used to work with and manage Docker objects. We’ve also looked briefly at some Docker image source code to see how the anonymous volumes associated with our project were created. In the next few posts, we’ll continue our investigation of Docker storage options with bind mounts in Part 4b – Exploring bind mounts in Docker Compose and named volumes in Part 4c – Exploring named volumes in Docker Compose before we get more practical by adding these to our WordPress app in Part 4d – Working with data storage in Docker Compose. When you’re ready to move on, make sure that you shut your project down with the docker-compose down -v command to prepare for the next tutorial.