> For the complete documentation index, see [llms.txt](https://camelgemonion.gitbook.io/docker/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://camelgemonion.gitbook.io/docker/docker-overview/docker-cun-chu/volume.md).

# volume

为了持久化由容器产生和使用的数据，优先选择卷来达到这个目的。bind mounts依赖主机操作系统的目录结构，而卷完全由容器进行管理。与bind mounts相比，卷有以下几点优势：

* 备份或迁移更加方便。
* 可以通过Docker CLI或Docker API对卷进行管理。
* 卷可以同时用于Linux容器和Windows容器。
* 可以在多个容器之间更加安全地共享数据。
* 卷驱动可以让我们在远端主机或云服务商存储卷，来加密卷中的内容或添加其他的功能。
* 新的卷中的内容可以被容器预先处理。
* 在Mac或Windows主机上，Docker Desktop的卷比起bind mounts拥有更高的性能。

对于持久化容器可写层的数据，卷通常是更好的一种方案。它不会增加容器的大小，并且卷中的内容与容器的生命周期是分开的。

![](/files/-MVv_EwNBYyORiDLPNQL)

&#x20;如果容器生成是非持久化的数据，我们可以考虑使用`tmfs mount`来避免将数据永久性地存储在某个地方，并且通过避免对容器的写入层进行写入而提升容器的性能。

卷使用`rprivate`绑定传播`(bind propagation)`，并且对于卷，绑定传播`(bind propagation)`无法配置。

### 选择-v还是--mount？

通常来说，`--mount`更加的明确和详细。最大的不同是`-v`语法将所有的选项组合在一起放入一个字段，而`--mount`语法则是分开的。

如果我们需要指定卷驱动选项，那么必须使用`--mount。`

`-v`或`--volume`: 由三个字段组成，通过:分隔开。字段必须按照正确的顺序组合。

* 如果是指定一个命名卷，第一个字段是卷的名称，并且这个名称在主机上是独一无二的。如果是匿           名卷，则第一个字段可以省略。
* 第二个字段指定容器上被挂载的文件或路径。
* 第三个字段是可选的，是一个逗号分隔的选项列表，例如`ro`。

`--mount`：由多个键值对组成，通过逗号进行分隔，每一个都是一个键值对组合。`--mount`语法比`-v`或`--volume`更详细，但是键的顺序没有那么重要，并且它所对应的值更容易被理解。

* 挂载的类型`(type)`可以是`bind`，`volume`或者`tmpfs`。
* 挂载的源`(source)`。对于命名卷，它是卷的名称。对于匿名卷，这个字段可以被省略。可以通过`source`或`src`两种形式来指定源。
* 挂载的目的地`(destination)`使用容器中被挂载的文件或目录的路径作为值。可以通过`destination`，`dst`或`target`三种形式来指定目的地。
* `readonly`选项如果存在，它会将`bind mount`以只读的形式挂载到容器中。
* `volume-opt`选项可以多次使用，使用选项名和值的键值对形式。

### -v和--mount之间行为的差异

与`bind mount`相反，卷即可以使用`-v`也可以使用`--mount`。当使用带有服务的卷时，只支持`--mount`。

### 创建和管理卷

与`bind mount`不同，卷可以在容器外部被创建和管理。

```
$ docker volume create my-vol
```

列出卷：

```
$ docker volume ls

local               my-vol
```

检查一个卷：

```
$ docker volume inspect my-vol
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]
```

移除一个卷：

```
$ docker volume rm my-vol
```

### 启动一个带有卷的容器

我们在启动一个带有卷的容器时，如果卷还不存在，Docker会为我们创建一个卷。下面的实例将`myvol2`挂载到了容器中的`/app/`目录。

使用`--mount`的形式：

```
$ docker run -d \
  --name devtest \
  --mount source=myvol2,target=/app \
  nginx:latest
```

使用`-v`的形式：

```
$ docker run -d \
  --name devtest \
  -v myvol2:/app \
  nginx:latest
```

使用`docker inspect devtest`来检查一个卷是否挂载正确。查看`Mounts`部分：

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

上面的信息显示挂载的是一个卷，显示了正确的源和目的地，并且挂载是`read-write`的。

停止一个容器，然后移除卷。这里需要注意移除一个卷的正确步骤！

```
$ docker container stop devtest

$ docker container rm devtest

$ docker volume rm myvol2
```

### 在docker-compose中使用卷

带有卷的`docker-compose`服务看起来应该是下面这样的：

```
version: "3.8"
services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:
```

在我们第一次调用`docker-compose up`时，卷会被创建。后续的调用会重复使用相同的卷。

一个卷也可以直接在`docker-compose`外部通过`docker volume create`创建，然后在`docker-compose.yml`中引用：

```
version: "3.8"
services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:
    external: true
```

### 启动一个带有卷的服务

当我们启动一个服务和定义一个卷时，每一个服务容器使用自己的本地卷。如果使用本地`(local)`卷驱动, 容器之前无法共享存储，但是有一些的卷驱动支持共享存储。

下面的示例启动一个带有四个副本的`nginx`服务，每一个服务都使用名字叫`myvol2`的本地卷。

```
$ docker service create -d \
  --replicas=4 \
  --name devtest-service \
  --mount source=myvol2,target=/app \
  nginx:latest
```

使用`docker service ps ps devtest-service`来检查正在运行的服务：

```
$ docker service ps devtest-service

ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
4d7oz1j85wwn        devtest-service.1   nginx:latest        moby                Running             Running 14 seconds ago
```

移除服务：

```
$ docker service rm devtest-service
```

### 针对服务语法的不同

`docker service create`命令不支持`-v`或`--volume`。当挂载一个卷到服务的容器时，必须使用`--mount`。

### 使用容器填充一个卷

当启动一个使用新创建的卷的容器时，被挂载的目录里面包含了容器的文件和一些目录(例如上面所说的/app/)，被挂载目录的内容会被拷贝到卷中。容器会进一步挂载和使用卷，其他使用该卷的容器也可以访问这些预填充的内容。

为了解释上面所说的，下面的示例启动了一个nginx容器，并且使用容器中/usr/share/nginx/html目录中的内容填充了新的nginx-vol卷。该目录默认保存nginx的默认HTML内容。

使用--mount：

```
$ docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html \
  nginx:latest
```

使用-v：

```
$ docker run -d \
  --name=nginxtest \
  -v nginx-vol:/usr/share/nginx/html \
  nginx:latest
```

运行完上面的示例以后，依然按照正确的步骤删除卷。

```
$ docker container stop nginxtest

$ docker container rm nginxtest

$ docker volume rm nginx-vol
```

### 使用一个只读卷

对于一些应用开发，容器需要对bind mount进行写入，以便写入以后产生的更改可以传播到容器主机。在其他时候，容器只需要访问这些数据。在同一时间，多个容器可以挂载到相同的卷，对于一些容器可以使用read-write形式挂载而其他的容器使用read-only形式挂载。

下面的示例通过在前面使用过的命令选项列表中添加ro，来使卷作为只读卷挂载到容器中的指定路径。

```
$ docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
  nginx:latest
```

通过`docker inspect nginxtest | jq '.[] | .Mounts[]'`来查看被挂载的卷：

```
{
        "Type": "volume",
        "Name": "nginx-vol",
        "Source": "/var/lib/docker/volumes/nginx-vol/_data",
        "Destination": "/usr/share/nginx/html",
        "Driver": "local",
        "Mode": "",
        "RW": false,
        "Propagation": ""
    }
```

停止和移除容器，然后移除卷。卷删除必须按照单独有序的步骤执行。

```
$ docker container stop nginxtest

$ docker container rm nginxtest

$ docker volume rm nginx-vol
```

### 在主机之间共享数据

当构建零容错的应用时，我们可能需要为服务配置多个副本来访问相同的文件。

![](/files/-MW-y7luIV_BH8lwdeKr)

当开发应用时，我们有多种方式来实现数据共享。一个是在我们的应用中添加代码逻辑，使文件存储在像AWS S3这样的存储系统中。另外一种方式是通过一个支持将文件写入到外部存储系统(例如NFS或AWS S3)的驱动来创建一个卷。

### 使用一个卷驱动

当我们使用docker volume create或当我们启动一个使用还没有创建的卷的容器时，我们可以指定一个卷驱动。下面的示例使用vieus/sshfs卷驱动。

#### 初始化设置

下面示例假设我们有两个节点，第一个节点可以使用ssh连接到第二个节点。

在docker主机上安装vieux/sshfs插件：

```
$ docker plugin install --grant-all-permissions vieux/sshfs
```

#### 使用卷驱动创建一个卷

下面示例指定了一个SSH密码，但是如果两个主机共享配置的key，我们可以省略密码。每个卷驱动可以有零个或多个可配置的选项，每一个选项使用-o指定。·

```
$ docker volume create --driver vieux/sshfs \
  -o sshcmd=test@node2:/home/test \
  -o password=testpassword \
  sshvolume
```

#### 启动一个使用通过卷驱动创建的卷的容器

下面示例指定了一个SSH密码，但是如果两个主机共享配置的key，我们可以省略密码。每个卷驱动可以有零个或多个可配置的选项。如果卷驱动需要我们传递选项，我们必须使用`--mount`来挂载卷而不是`-v`。

```
$ docker run -d \
  --name sshfs-container \
  --volume-driver vieux/sshfs \
  --mount src=sshvolume,target=/app,volume-opt=sshcmd=test@node2:/home/test,volume-opt=password=testpassword \
  nginx:latest
```

#### 创建一个创建NFS卷的服务

下面示例展示了创建一个服务时如何创建一个NFS卷。示例使用10.0.0.10作为NFS服务器地址，而/var/docker-nfs作为被导出的目录。注意这里的卷驱动指定为local。

NFSV3

```
$ docker service create -d \
  --name nfs-service \
  --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,volume-opt=o=addr=10.0.0.10' \
  nginx:latest
```

NFSV4

```
docker service create -d \
    --name nfs-service \
    --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,"volume-opt=o=addr=10.0.0.10,rw,nfsvers=4,async"' \
    nginx:latest
```

### 备份，恢复或迁移数据卷

卷对于备份，恢复和迁移非常有用。使用--volumes-from来创建一个挂载到卷上面的新容器。

#### 备份一个容器

例如，创建一个dbstore的容器：

```
$ docker run -v /dbdata --name dbstore ubuntu /bin/bash
```

下面的命令中，我们：

* 启动一个新的容器，然后挂载到dbstore容器的卷上面。
* 挂载一个本地主机目录/backup。
* 传递一个命令到容器中将dbdata卷中的内容打包成backup.tar文件并保存在/dbdata中。

```
$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
```

#### 从备份中恢复容器

通过上面备份的数据，我们可以在相同的容器中或者你在其他地方创建的容器中恢复它。

例如，创建一个名为dbstore2的容器：

```
$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
```

然后在新的容器的数据卷中解压备份文件：

```
$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
```

#### 移除卷

一个Docker数据卷在容器被删除以后依然存在。有两种类型的卷需要了解：

* 命名卷有一个来自容器外部的指定的源，例如awesome:/bar。
* 匿名卷没有指定的源，因此当容器被删除时，需要通过命令让Docker引擎去移除它们。

#### 移除匿名卷

如果需要自动删除匿名卷，可以使用-rm选项。例如，下面的命令创建了一个匿名/foo卷，当容器被删除时，Docker引擎会移除/foo卷，但是不会移除awesome卷。

```
$ docker run --rm -v /foo -v awesome:/bar busybox top
```

#### 移除所有卷

移除所有卷来释放空间，我们可以使用下面的命令：

```
$ docker volume prune
```
