# 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
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://camelgemonion.gitbook.io/docker/docker-overview/docker-cun-chu/volume.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
