1. Overview

Docker has multiple options to persist and share data for a running container. However, we may need more than one file storage for a running container, for example, to create backups or grant different access. Or for the same container, we may need to add named volumes and bind them to specific paths.

In this tutorial, we’ll see how to mount multiple volumes on a container. We’ll see some examples both with the command line and with Docker Compose.

2. Multiple Mounts on a Docker Container

Docker uses Storage to persist data, so we are not losing our information if a container restarts. Furthermore, data persistence will be relevant if we want to share in a clustered environment.

We’ll create some examples using Volumes and Bind mounts to highlight the most common development use cases.

2.1. Using Multiple Volumes

First, let’s create two different named volumes:

docker volume create --name first-volume-data && docker volume create --name second-volume-data

Suppose we want to mount two different volumes for our web application, but one of those paths must be read-only.

If we are using the command line, we can use the -v option:

docker run -d -p 8080:8080 -v first-volume-data:/container-path-1 -v second-volume-data:/container-path-2:ro --name web-app web-app:latest

We can also use anonymous volumes, for example, by including -v container-path. Docker will create it for us, but it gets removed once we delete the container.

Let’s inspect our container to check if our mounts are correct:

docker inspect 0050cda73c6f

We can see relevant information like sources and destinations, types, and also a read-only status for the second volume:

"Mounts": [
  {
      "Type": "volume",
      "Name": "first-volume-data",
      "Source": "/var/lib/docker/volumes/first-volume-data/_data",
      "Destination": "/container-path-1",
      "Driver": "local",
      "Mode": "z",
      "RW": true,
      "Propagation": ""
  },
  {
      "Type": "volume",
      "Name": "second-volume-data",
      "Source": "/var/lib/docker/volumes/second-volume-data/_data",
      "Destination": "/container-path-2",
      "Driver": "local",
      "Mode": "z",
      "RW": false,
      "Propagation": ""
  }
]

Similarly, Docker recommends we use the –mount option:

docker run -d \
  --name web-app \
  -p 8080:8080 \
  --mount source=first-volume-data,target=/container-path-1 \
  --mount source=second-volume-data,target=/container-path-2,readonly \
  web-app:latest

The result is the same as the -v option. However, besides better clarity, –mount is the only way we can use volumes with Services when we are in Swarm mode.

So, if we want to create a service for our web app which has multiple mounts, we need to use the –mount option:

docker service create --name web-app-service \
  --replicas 3 \
  --publish published=8080,target=80 \
  --mount source=first-volume-data,target=/container-path-1 \
  --mount source=second-volume-data,target=/container-path-2,readonly \
  web-app

Similarly, we can inspect our service:

docker service inspect web-app-service

Likewise, we’ll get some info about the container inside our service specifications:

"Mounts": [
  {
      "Type": "volume",
      "Source": "first-volume-data",
      "Target": "/container-path-1"
  },
  {
      "Type": "volume",
      "Source": "second-volume-data",
      "Target": "/container-path-2",
      "ReadOnly": true
  }
]

2.2. Using Volumes and Bind Mounts

We may also want to use a volume alongside a mount to a specific folder or file in our host.

Suppose we have a MySQL database image, and we need to run an initial script to create a schema or populate it with some data:

docker run -d \
  --name db \
  -p 3306:3306 \
  --mount source=first-volume-data,target=/var/lib/mysql \
  --mount type=bind,source=/init.sql,target=/docker-entrypoint-initdb.d/init.sql \
  mysql:latest

If we inspect our container, we can now see two different mount types:

"Mounts": [
  {
      "Type": "volume",
      "Name": "first-volume-data",
      "Source": "/var/lib/docker/volumes/first-volume-data/_data",
      "Destination": "/var/lib/mysql",
      "Driver": "local",
      "Mode": "z",
      "RW": true,
      "Propagation": ""
  },
  {
      "Type": "bind",
      "Source": "/init.sql",
      "Destination": "/docker-entrypoint-initdb.d/init.sql",
      "Mode": "",
      "RW": true,
      "Propagation": "rprivate"
  }
]

2.3. Using Multiple Bind Mounts

Similarly, we can use multiple bind mounts, for example, when we use Localstack, a local AWS emulator.

Suppose we want to start a local S3 service:

docker run --name localstack -d \
  -p 4563-4599:4563-4599 -p 8055:8080 \
  -e SERVICES=s3 -e DEBUG=1 -e DATA_DIR=/tmp/localstack/data \
  -v /.localstack:/var/lib/localstack -v /var/run/docker.sock:/var/run/docker.sock \
  localstack/localstack

While inspecting the container, we see multiple binds in our host configuration:

"Binds": [
  "/.localstack:/var/lib/localstack",
  "/var/run/docker.sock:/var/run/docker.sock"
]

3. Docker Compose

Let’s see a more compact way for multiple mounts with Docker Compose.

3.1. Using Multiple Volumes

First, let’s start with two volumes, as shown in our YAML template:

services:
  my_app:
    image: web-app:latest
    container_name: web-app
    ports:
      - "8080:8080"
    volumes:
      - first-volume-data:/container-path-1
      - second-volume-data:/container-path-2:ro

volumes:
  first-volume-data:
    driver: local
  second-volume-data:
    driver: local

Once we define the volumes we created earlier, we can add the mount in the service in the form of named-volume:container-path.

The Long syntax is also available for Docker Compose, for example:

volumes:
  - type: volume
    source: volume-data
    target: /container-path

3.2. Using Volumes and Bind Mounts

Again, here’s an example of using a MySQL service:

services:
  mysql-db:
    image: mysql:latest
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_ROOT_HOST=localhost
    ports:
      - '3306:3306'
    volumes:
      - db:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db:
    driver: local

3.3. Using Multiple Bind Mounts

Finally, let’s convert our previous Localstack example to Docker Compose:

services:
  localstack:
    privileged: true
    image: localstack/localstack:latest
    container_name: localstack
    ports:
      - '4563-4599:4563-4599'
      - '8055:8080'
    environment:
      - SERVICES=s3
      - DEBUG=1
      - DATA_DIR=/tmp/localstack/data
    volumes:
      - './.localstack:/var/lib/localstack'
      - '/var/run/docker.sock:/var/run/docker.sock'

4. Conclusion

In this article, we’ve seen how to create multiple mounts using Docker.

We’ve seen some combinations of bind mounts and named volumes. We have also seen use cases using the Docker command line and Docker Compose.

As always, we can find working code examples over on GitHub.

Comments are closed on this article!