[Part 1] - Docker Volumes - Bind Mounts


As we saw in the previous video, Docker Containers do allow the storage of persistent data.

However, the data inside a container exists only for the lifetime of that particular container instance.

This is fine if all we ever do is stop and start that container.

But if the container is deleted, any data inside that container is also deleted.

Also, as more data is added to a container, that container grows in size. In larger organisations where storage is centralised and optimised, this may make sysadmins unhappy.

What appears a more optimal solution is to separate our Docker Containers from the data that these containers read from, and write too.

Enter: Docker Volumes

We visit Volumes at an interesting time.

As we shall soon see when we get to looking at docker-compose in more detail, there is a transition underway from the way Docker Volumes are currently predominantly used, to the 'new' way.

As it happens, the 'new' way is not new at all.

Currently most examples to be found online - and therefore most real world dev setups I've come across - tend to use bind mounts.

And the 'new' way is to actually use volumes.

There is a third way of mounting data which is to use tmpfs mounts, which we won't be going into here. tmpfs mounts are stored in the Docker host's memory and are never written to underlying storage. I have never needed to use tmpfs, and as such have no further experience to share.

Docker Bind Mounts vs Docker Volumes

The logical next question then is:

What is the difference between a Docker Volume, and a Docker Bind Mount?

First up, it's important to understand that however we choose to present (or mount) data into our running containers, from the perspective of the container, they all behave identically.

The differences become important outside of the running container.

Bind mounts allow you to take a directory or individual file that exists on your machine (from herein called the Host, or Docker Host) and access that directory or file inside the running container. Any changes you make to the directory contents, or individual file will cause the file(s) to change on the Host machine.

This is perhaps best illustrated with an example:

$ docker run -d nginx

ebc4aff07132d52241d2a93a22aa6a07fb7818f58495b123ffe6e195e8bfe908

$ docker exec ebc4a ls -la /var/www/madeup

ls: cannot access '/var/www/madeup': No such file or directory

We've started a container, and by default the /var/www/madeup directory does not exist. As it shouldn't, we just made it up :)

Next, we are going to use a "bind mount" to present the /var/www/madeup directory inside our container.

First let's create a new directory on our local / Docker Host machine:

$ pwd
/tmp/docker-test

$ ls -la
total 40
drwxrwxr-x  2 chris chris  4096 Sep  2 09:39 .
drwxrwxrwt 13 root  root  36864 Sep  2 12:50 ..

$ touch my-file
$ echo "hello world" >> my-file
$ cat my-file
hello world

We haven't done anything with Docker at this point. This is all setup so we have something to see once we do the mount.

We are in the /tmp/docker-test directory.

From here we created a new file: my-file. Into that file we add a single string: "hello world".

Now, let's bind mount this into a Docker container:

# first let's stop the running container
$ docker stop ebc4a
ebc4a

# now let's start a new container, this time with a bind mount
# the `\` allows the command to be split over multiple lines, but
# still appear to the system as one single command
$ docker run -d \
  --mount type=bind,source=/tmp/dockertest/,target=/var/www/madeup \
  nginx

3ee49a64099d005524f80303f0b3c1355566512c7c0f67234e54bb22b795421d

# and see if the contents are as expected
$ docker exec 3ee49a ls -la /var/www/madeup

total 12
drwxrwxr-x 2 1000 1000 4096 Sep  2 11:50 .
drwxr-xr-x 3 root root 4096 Sep  2 12:02 ..
-rw-rw-r-- 1 1000 1000   12 Sep  2 11:50 my-file

$ docker exec 3ee49a cat /var/www/madeup/my-file
hello world

# now if we delete the file from the running container
$ docker exec 3ee49a rm /var/www/madeup/my-file

# it's gone from the container
$ docker exec 15 cat /var/www/madeup/my-file

cat: /var/www/madeup/my-file: No such file or directory

# and from the host
$ ls -la

total 40
drwxrwxr-x  2 chris chris  4096 Sep  2 20:33 .
drwxrwxrwt 14 root  root  36864 Sep  2 20:30 ..

Awesome.

There's upsides and downsides to this.

We had to stop the running container to bind mount.

And we ended up with a new container instance after we ran the container with the bind mount.

But this, in a way, is part of the beauty of containers. The particular instance of a container shouldn't matter to us that much. They are cheap. They can be created and disposed of without much thought.

More interesting here is the use of the --mount flag.

In many online examples you will see the use of -v instead. -v here means volume.

The syntax itself is confusing in my opinion, and is part of the reason why I said earlier that we are in interesting times.

There is a transition in progress with regards to using --mount, or -v. You really don't need to dwell on this, but reading a little further may be helpful as currently -v is more commonly used.

The new syntax makes it somewhat more obvious that we are dealing with bind mounts and volumes as individual concepts. It can certainly be confusing to use the -volume syntax to mount a bind mount... yikes.

Thankfully a large part of this process will be abstracted away from us when we get to docker-compose, which is where you will likely spend the vast majority of your time. However, understanding (or at least being aware of) this from the command line is extremely helpful in working effectively with Docker.

A big reason why bind mounts are popular is that they are simple and obvious.

We can see data on our host machine.

We know the absolute path to this data. In the example above we had the absolute path of /tmp/docker-test. We can easily browse to this on our host machine.

Heck, even when we delete data from either our host, or the running container, it is clear as to what is happening. This is how the filesystems we are used to tend to work. We feel comfortable.

From all of this, bind mounts seem like a decent solution.

They work well, they are obvious, and as long as you have a decent spec HDD (e.g. an SSD) then they are extremely performant, in my experience.

Yet they are no longer the recommended approach.

Docker Volumes: The Future

All of the things that make Docker bind mounts so easy to use are at odds with Docker's bigger picture.

By utilising Docker we gain the ability to spin up many instances of our containers potentially across multiple hosts. We can commit our docker-compose.yml files to the project's git repository and our colleagues can rapidly spin up their own container instances and reproduce our development environments.

Only, they can't.

At least, not if we got a little too specific.

Remember that a bind mount takes an absolute path?

What if that path doesn't exist on your colleagues machine?

What if they don't even use a unix-like operating system? What if they use Windows?

They would need to make custom changes to the docker-compose.yml file, which defeats the purpose of what we are trying to do, and brings a bunch of unwanted caveats to proceedings. Whose docker-compose.yml file is now correct? The one with the Windows-esque paths, or your original with unix-like paths? Pain!

Docker Volumes remove this problem.

Code For This Course

Get the code for this course.

Episodes