1. 概述

Docker 容器本质上是在 Linux 系统中隔离运行的环境,它能分配预定义的资源。我们通常使用 Docker 容器来隔离运行应用程序。但默认情况下,容器一旦停止,其内部的所有修改都会丢失。如果我们希望保留这些数据,可以使用 Docker 卷(Volume)或绑定挂载(Bind Mount)来实现。

在本文中,我们将学习 Docker 卷的基本概念,以及如何管理和挂载它们到容器中。


2. Docker 文件系统原理

Docker 容器是基于镜像运行的。镜像由多个只读层构成,这些层通过 Union Filesystem 合并成一个文件系统。当容器启动时,Docker 会在这些只读层之上添加一个可读写的层,使容器看起来像是一个完整的 Linux 文件系统。

但是,当容器停止或被删除后,这个读写层也会被清除,所以容器内的文件修改不会保留。

layers

我们可以用以下命令验证这一点:

$ docker run bash:latest \
  bash -c "echo hello > file.txt && cat file.txt"

输出:

hello

但如果我们再次运行容器并尝试读取这个文件:

$ docker run bash:latest bash -c "cat file.txt"
cat: can't open 'file.txt': No such file or directory

文件已经不存在了。因此,如果我们希望数据在容器重启后仍然保留,就需要使用 Docker 卷。


3. 绑定挂载 vs Docker 卷

绑定挂载和 Docker 卷都可以用于实现持久化存储,但它们的实现机制和适用场景有所不同。

  • 绑定挂载:将主机上的文件或目录挂载到容器中,适合需要直接访问主机文件的场景。性能高,但依赖主机的文件系统结构。
  • Docker 卷:由 Docker 管理的存储目录,通常位于 Docker 的存储目录中。它不依赖主机文件系统,具有更好的可移植性和管理性。

Docker 卷的优势

  • 更容易迁移和管理
  • 提供专用命令进行操作(如 docker volume
  • 支持跨平台(Windows 和 Linux)
  • 多容器共享更安全

绑定挂载的缺点

  • 依赖主机路径,移植性差
  • 容易因为路径错误导致问题
  • 安全性较低,容器可直接访问主机文件

⚠️ 总体建议:优先使用 Docker 卷,除非你有明确理由需要绑定挂载。


4. Docker 中的卷

Docker 卷是用于在容器重启之间持久化数据的一种机制。它既可以是 Docker 原生的卷,也可以是绑定挂载。

4.1 绑定挂载示例

绑定挂载用于将主机目录挂载到容器中。例如,我们可以将当前工作目录挂载到容器中,并在容器中创建文件:

$ docker run -v $(pwd):/var/opt/project bash:latest \
  bash -c "echo Hello > /var/opt/project/file.txt"
  • -v 参数指定挂载关系:
    • $(pwd):主机当前目录
    • /var/opt/project:容器内的挂载路径

执行后,你会在主机当前目录下看到 file.txt 文件。这说明容器和主机之间共享了该目录。

4.2 Docker 卷示例

Docker 卷是 Docker 自己管理的存储空间。它独立于主机文件系统,更适合在多个容器之间共享数据。

例如,我们先创建一个卷:

$ docker volume create data_volume
data_volume

然后启动一个容器并挂载该卷:

$ docker run -v data_volume:/var/opt/project bash:latest \
  bash -c "echo Baeldung > /var/opt/project/Baeldung.txt"

再次运行容器读取文件:

$ docker run -v data_volume:/var/opt/project bash:latest \
  bash -c "cat /var/opt/project/Baeldung.txt"
Baeldung

✅ 这说明即使容器重启,数据依然保留。


5. 卷的管理

Docker 提供了专门的命令来管理卷,包括创建、查看、删除等操作。

5.1 创建卷

$ docker volume create data_volume
data_volume

不指定名称时,Docker 会自动生成一个随机名称:

$ docker volume create
d7fb659f9b2f6c6fd7b2c796a47441fa77c8580a080e50fb0b1582c8f602ae2f

5.2 查看卷列表

$ docker volume ls
DRIVER     VOLUME NAME
local     data_volume
local   d7fb659f9b2f6c6fd7b2c796a47441fa77c8580a080e50fb0b1582c8f602ae2f

可以使用过滤器查看特定卷:

$ docker volume ls -f name=data
DRIVER     VOLUME NAME
local     data_volume

5.3 查看卷详细信息

$ docker volume inspect data_volume
[
  {
    "CreatedAt": "2020-11-13T17:04:17Z",
    "Driver": "local",
    "Labels": null,
    "Mountpoint": "/var/lib/docker/volumes/data_volume/_data",
    "Name": "data_volume",
    "Options": null,
    "Scope": "local"
  }
]

5.4 删除卷

$ docker volume rm data_volume
data_volume

⚠️ 注意:如果卷正在被某个容器使用,则无法删除。

5.5 清理未使用的卷

$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
data_volume

6. 启动容器时挂载卷

6.1 使用 -v 参数挂载

$ docker run -v data_volume:/var/opt/project bash:latest \
  bash -c "ls /var/opt/project"

如果卷为空,不会有任何输出。但我们可以写入文件并再次读取:

$ docker run -v data_volume:/var/opt/project bash:latest \
  bash -c "echo Baeldung > /var/opt/project/Baeldung.txt"
$ docker run -v data_volume:/var/opt/project bash:latest \
  bash -c "ls /var/opt/project"
Baeldung.txt

6.2 使用 --mount 参数挂载

--mount 提供了更清晰的语法:

$ docker run --mount \
  'type=volume,src=data-volume,\
  dst=/var/opt/project,volume-driver=local,\
  readonly' \
  bash -c "ls /var/opt/project"

参数说明:

  • type: 挂载类型(volume)
  • src: 源卷名
  • dst: 容器内挂载路径
  • volume-driver: 存储驱动(默认 local)
  • readonly: 只读挂载(如需可写,使用 rw

⚠️ 注意:--mount 会在卷不存在时自动创建。

6.3 使用 --volumes-from 共享卷

如果你有一个已经退出的容器,可以通过 --volumes-from 将其挂载的卷复制到新容器中:

$ docker run --volumes-from 4920 \
  bash:latest \
  bash -c "ls /var/opt/project"
Baeldung.txt

✅ 这在容器间共享数据时非常有用,例如 Jenkins 容器之间的数据共享。


7. 总结

Docker 默认不会持久化容器内的数据。为了实现数据持久化,我们可以使用绑定挂载或 Docker 卷。

  • 绑定挂载:适合需要直接访问主机文件的场景,性能高但可移植性差
  • Docker 卷:由 Docker 管理,更适合跨容器共享和生产环境使用

本文介绍了 Docker 卷的创建、管理和挂载方式,以及如何使用 --mount--volumes-from 实现更灵活的数据共享。

掌握 Docker 卷的使用,对于构建持久化、可扩展的容器化应用至关重要。


原始标题:Guide to Docker Volumes