Understanding Docker Volumes

22 minute read     Updated:

Shingai Zivuku %
Shingai Zivuku

This article discusses Docker volumes management. Managing complex Docker builds, Earthly’s caching and parallelization save time. Learn more about Earthly.

Docker is a common containerization solution that offers a user-friendly interface. It allows you to deploy your application as a lightweight process set rather than a complete virtual machine.

Docker images are like a snapshot of a container’s file system and contain both your application and its dependencies. When you run it, you recreate the container’s state. You don’t have to be concerned about setting up your environment because running an image recreates everything for you and is isolated from your operating system and other running containers.

The Docker interface is simple and users can easily create and implement applications into their containers or carry out version management, copy, share, and modify, just like managing ordinary code.

However, containers often need to use data beyond their container or share data between containers. While it may be tempting to rely on the host file system, a better solution is to work with persistent data in a container, namely Docker volumes.

A Docker volume is an independent file system entirely managed by Docker and exists as a normal file or directory on the host, where data is persisted.

In this guide, you’ll learn how volumes work with Docker, what they do, and what the best practices are for keeping them secure and effective.

What Are Docker Volumes

The purpose of using Docker volumes is to persist data outside the container so it can be backed up or shared.

Docker volumes are dependent on Docker’s file system and are the preferred method of persisting data for Docker containers and services. When a container is started, Docker loads the read-only image layer, adds a read-write layer on top of the image stack, and mounts volumes onto the container filesystem.

Why Docker Volumes?

If you are using Docker for development, you must be familiar with the -v or --volume flag that lets you mount your local files into the container. For instance, you can mount your local ./target onto the /usr/share/nginx/html directory container or an nginx container to visualize your html files.

echo "<h1>Hello from Host</h1>" > ./target/index.html
docker run -it --rm --name nginx -p 8080:80 -v "$(pwd)"/target:/usr/share/nginx/html nginx

Navigate to http://localhost:8080/ and you should see “Hello from Host”.

This is called a bind mount and is commonly used by developers. But, if you are using Docker Desktop on Windows or MacOS bind, mounts have significant performance issues. As a result, using volumes may be the best alternative for holding state between container runs.

Unlike bind mount, where you can mount any directory from your host, volumes are stored in a single location (most likely /var/lib/docker/volumes/ on unix systems) and greatly facilitates managing data (backup, restore, and migration). Docker volumes can safely be shared between several running containers.

You can also save data to a remote server or in cloud Docker volumes with alternative volume drivers like sshfs.

In addition, Docker enables you to manage volume with the command line docker volume, making their management simple.

Creating and Managing Docker Volumes

In this section, you’ll learn how to create a Docker volume implicitly and explicitly and then declare it from a Docker file. Then you’ll learn how to view a data volume, mount it to a container, and configure it using docker-compose.

Create a Docker Volume Implicitly

The easiest way to create and use a volume is with docker run and the -v or --volume flag. This flag takes three arguments separated by ::

-v <source>:<destination>:<options>

If the “source” is a path that was used in the previous example, Docker will use a mount bind. If the “source” is a name, then Docker tries to find this volume or creates one if one cannot be found. Below, the previous example has been updated to use a volume instead of a mount bind:

docker run -it --rm --name nginx -p 8080:80 -v demo-earthly:/usr/share/nginx/html nginx

You can check to make sure the container was properly created with docker volume ls which lists all existing volumes.

docker volume ls

Note that the volume in question is not empty. If a volume is completely empty, the container’s content is copied to the volume.

You can check the status of your volumes on Linux. This gives you a chance to see where volumes are stored:

ls /var/lib/docker/volumes/target/_data/demo-earthly

On Mac and Windows it’s a bit more tricky. In order to keep things simple, you can mount the volume on an ubuntu container and use ls to see the content of your volume:

docker run -it --rm -v demo-earthly:/opt/demo-earthly ubuntu ls /opt/demo-earthly

Create a Docker Volume Explicitly

Alternatively you can use the docker volume create command to explicitly create a data volume. This command gives you the option to choose and configure the volume driver. The implicit creation of volumes always uses the local driver with default settings.

docker volume create --name demo-earthly

Declare a Docker Volume from Dockerfile

Volumes can be declared in your Dockerfile using the VOLUME statement. This statement declares that a specific path of the container must be mounted to a Docker volume. When you run the container, Docker will create an anonymous volume (volume with a unique id as the name) and mount it to the specified path.

FROM nginx:latest

RUN echo "<h1>Hello from Volume</h1>" > /usr/share/nginx/html/index.html
VOLUME /usr/share/nginx/html

Lets build and run your new image:

docker build -t demo-earthly .
docker run -p 8080:80  demo-earthly

You can now validate that nginx serves your message at http://localhost:8080/.

More importantly, an anonymous Docker volume has been created, and every time you start a new container, another volume is created with the content of /usr/share/nginx/html.

docker volume ls

From the above example, ​​a volume directory data with the text file test containing “Hello from Volume” is created.

View a Data Volume

To manage your data, sometimes you need to list data volumes from the command line as a point of reference, which is faster than repeatedly checking the configuration files. You can use the docker volume ls command to view a list of data volumes.

docker volume ls
Use the docker volume inspect command to view the data volume details.
docker volume inspect

Mount a Volume to a Container

As you have seen through the various examples -v and --volume are the most common way to mount a volume to a container using the syntax:

-v <name>:<destination>:<options>

One notable option is ro which means that the volume will be mounted as read-only:

docker run -it -v demo-volume:/data:ro ubuntu

Try to write into the folder/data to validate that the volume is in read-only mode:

echo "test" > /data/test

An alternative to -v is to add the —mount option to the docker run command. --mount is the more verbose counterpart of -v.

To launch a container and mount a data volume to it, follow this syntax:

docker run --mount source=[volume_name],destination=[path_in_container] [docker_image]

Replace [path in container] with the path to attach the Docker volume [volume_name] in the container.

For example, run the following command to start an Ubuntu container and mount the data volume to it.

docker run -it --name=example --mount source=demo-volume,destination=/data ubuntu

Remember if the volume doesn’t exist Docker will create it for you.

List the contents of the container to see if the volume is mounted successfully. You should find the Docker volume name defined in the above data syntax.

Container content

Configure a Volume Using docker-compose

Although there are many ways to create a volume, it’s more convenient to use the docker-compose command to easily share data between multiple containers.

The use of the volume property in compose files is very similar to -v and --volume. That being said, to perform a bind mount (mount a directory from your local machine), you can use a relative path unlike -v with the command docker run that requires an absolute path.

version: "3.2"
services:
  web:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - ./target:/usr/share/nginx/html

The containers and hosts in the above configuration use volumes in the services definition (web) to mount ./target from the host to /usr/share/nginx/html of the container. As with the first example, if you navigate to http://localhost:8080/ you should read “Hello from Host”.

With docker-compose, volumes must be declared at the same level as services. Then you can refer to them by their name.

version: "3.2"
services:
  web:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - html_files:/usr/share/nginx/html
  web1:
    image: nginx:latest
    ports:
      - 8081:80
    volumes:
      - html_files:/usr/share/nginx/html
 
volumes:
  html_files:

In this example, you declared a volume named html_files and used it in both web and web1 service. Multiple containers can mount the same volume.

Running docker-compose up will create a volume named <project_name>_html_files if it doesn’t already exist . Then run docker volume ls to list the two volumes created, starting with the project name.

You can also manage container outside of you docker-compose file, but you still need to declare them under volumes and set the property external: true.

version: "3.2"
services:
  web:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - html_files:/usr/share/nginx/html
 
volumes:
  html_files:
    external: true

If you don’t have html_files, you can use docker volume create html_files to create it. When you add external, Docker will find out if the volume exists; but if it doesn’t, an error will be reported.

Copy Files Between Containers from a Shared Volume

Let’s look at how Docker volumes enable file sharing across containers.

In this example, use the volume and container we previously defined and execute the following commands:

docker create volume demo-earthly
docker run -it --name=another-example --mount source=demo-volume,destination=/data ubuntu

Navigate to the data volume directory and create a file using the command touch demo.txt. Exit the container, then launch a new container another-example-two with the same data volume:

docker run -it --name=another-example-two --mount source=demo-volume,destination=/data ubuntu

The demo.txt file you created in the preceding container should list another-example in the output.

Copying files

Docker Volume Best Practices

Now that you’ve learned how to implement Docker volumes, it’s important to keep in mind a few best practices:

Conclusion

Often, you want your containers to use or persist data beyond the scope of the container’s lifetime. You can use volumes to solve this problem by working with Docker to mount, create, and share volumes between containers.

In this guide, you looked at how volumes work with Docker, what they do, and where volumes are the preferred solution.

Docker plays an important role in the DevOps ecosystem, so if you want to improve your continuous integration process, consider Earthly. Earthly is a build automation tool that allows you to build anything via containers, making your builds self-contained, repeatable, portable, and parallel.

If you enjoyed this tutorial you might want to grab a copy the free eBook “Docker Fundamentals”. It covers the essentials of Docker, including volumes, networking, secrets management, logging, multistage builds, debugging, and even practical use cases like integration with Visual Studio Code and GitHub Actions. Download the free Docker Fundamentals ebook right now.
Shingai Zivuku %
Shingai Zivuku
Shingai is a DevOps Security Engineer who lives in Nairobi. He’s passionate about learning new things. In his free time, he’s probably trying something new, reading, or catching up with the latest breaking news.

Updated:

Published:

Get notified about new articles!
We won't send you spam. Unsubscribe at any time.