Docker 容器数据卷

什么是容器数据卷

容器数据卷是 Docker 中用于持久化存储容器数据的一种机制。简单来说,数据卷就像是容器与宿主机之间的 “共享文件夹”

Docker 将应用与运行的环境打包形成容器运行, Docker容器产生的数据,如果不通过 docker commit 生成新的镜像,使得数据做为镜像的一部分保存下来, 那么当容器删除后,数据自然也就没有了。为了能保存数据在Docker中我们使用容器数据卷。也为了避免容器内部数据难以与外部(宿主机或其他容器)交互的问题。

卷就是目录或文件,存在于一个或多个容器中,由Docker挂载到容器,但卷不属于联合文件系统(Union FileSystem),因此能够绕过联合文件系统提供一些用于持续存储或共享数据的特性:。

卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。

image-20250715105901984

数据卷的相关信息

数据卷的特点:

  1. 数据卷可在容器之间共享或重用数据,容器可以读写数据卷中的内容,宿主机也可以直接访问这些数据
  2. 卷中的更改可以直接生效
  3. 数据卷中的更改不会包含在镜像的更新中
  4. 数据卷的生命周期一直持续到没有容器使用它为止
  5. 即使容器被删除,数据卷中的数据依然保留

数据卷的核心特性:

  1. 持久性:数据卷独立于容器生命周期,容器删除后数据依然保留
  2. 可共享性:一个数据卷可以同时被多个容器挂载
  3. 可重用性:数据卷可以在不同容器之间重复使用
  4. 高性能:数据卷直接映射到宿主机文件系统,IO 性能接近原生
  5. 跨平台:在不同操作系统的 Docker 环境中表现一致

Docker 提供了三种主要的数据卷类型:

类型 特点 适用场景
命名卷(Named Volumes) 由 Docker 管理,有唯一名称,存储在 /var/lib/docker/volumes/ 大多数持久化存储需求,如数据库数据
匿名卷(Anonymous Volumes) 由 Docker 管理,无名称,使用随机 ID 标识 临时数据存储,或不希望手动管理的场景
绑定挂载(Bind Mounts) 将宿主机任意目录直接挂载到容器 需要明确指定宿主机目录的场景,如开发环境

数据卷的基本操作

  1. 创建命名卷

    1
    2
    # 创建一个名为myvolume的数据卷
    docker volume create myvolume
    image-20250715102523021
  2. 查看数据卷列表

    1
    docker volume ls
    image-20250715101408265

    这就是本地数据卷,默认驱动,一般是 /var/lib/docker/volumes/ 下对应数据卷名的目录,一长串哈希值形式的名称通常是匿名数据卷,匿名数据卷一般是在创建容器时,没显式指定数据卷名称,Docker 自动生成的(比如运行容器时用 -v /容器内路径 这种简单挂载方式,没给数据卷命名,就会生成匿名卷 )。

  3. 查看数据卷详情

    1
    docker volume inspect myvolume
    image-20250715101515655
  4. 挂载数据卷到容器

    1
    2
    # 将myvolume挂载到容器的/data目录
    docker run -d --name mycontainer -v myvolume:/data nginx
    image-20250715102553957

    这个意思就是把名为 ,myvolume 的数据卷(如果不存在会自动创建),挂载到名为 mycontainer容器内的 /data 目录 ,实现宿主机和容器、或容器间的数据持久化 / 共享。

    来测试一下挂载的情况,用 docker exec -it mycontainer /bin/bash 进入容器,看看 /data 目录是否存在,尝试创建个文件,然后到宿主机上查看数据卷实际路径(通过 docker volume inspect myvolumeMountpoint 字段对应的目录 ),看看有没有 test.txt

    image-20250715102948823

    可以看到mycontain容器的myvolume数据卷的实际挂载位置中有这个目录

    image-20250715103103301
  5. 挂载宿主机目录(绑定挂载)

    1
    2
    3
    4
    # 将宿主机的/path/to/host/dir挂载到容器的/data目录
    docker run -d --name mycontainer -v /path/to/host/dir:/data nginx

    docker run -d --name 容器名 -v /宿主机目录:/容器内目录

    先创建了一个目录,然后再挂载到特定目录上

    image-20250715104306499

    验证容器到宿主机的写入测试

    1
    2
    3
    4
    5
    6
    7
    8
    # 进入容器,在 /data 目录创建文件
    docker exec -it mycontainer bash
    # 进入容器后执行:
    echo "Hello from Container" > /data/container-test.txt
    exit # 退出容器

    # 检查宿主机目录是否有文件
    cat ~/docker-data/container-test.txt
    image-20250715104425084

    当然这个测试也可以反着来

  6. 删除数据卷

    1
    2
    3
    4
    5
    # 删除指定数据卷
    docker volume rm myvolume

    # 删除所有未使用的数据卷
    docker volume prune
    image-20250715103951491
    image-20250715104010631
  7. 只读挂载

    可以将数据卷以只读方式挂载到容器,防止容器修改数据:

    1
    docker run -d --name mycontainer -v myvolume:/data:ro nginx

    忘删容器了,docker rm -f mycontainer

    image-20250715104806188

    还是换个名子吧,可以看到新建的容器的哈希id,可以看到想要修改没有权限,只读验证

    image-20250715105434802
    image-20250715105658126

    可以看到输出 Hello from Host 则表示 宿主机 → 容器 读取成功(只读不影响 “读”)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    docker ps
    docker logs mycontainer
    # 进入容器,尝试在 /data 写文件
    docker exec -it mycontainer bash
    # 进入容器后执行:预期报错:bash: /data/test.txt: Read-only file system
    echo "Try to write" > /data/test.txt
    # 在宿主机数据卷目录写文件(先找数据卷路径):
    # 查看 myvolume 数据卷详情
    docker volume inspect myvolume
    echo "Hello from Host" > /var/lib/docker/volumes/myvolume/_data/host-test.txt
    # 读取文件
    docker exec -it mycontainer bash
    # 进入容器后执行:
    cat /data/host-test.txt
  8. 共享数据卷(多容器共享)

    多个容器可以挂载同一个数据卷实现数据共享

    1
    2
    3
    4
    5
    # 第一个容器挂载数据卷
    docker run -d --name container1 -v sharedvolume:/data nginx

    # 第二个容器挂载同一个数据卷
    docker run -d --name container2 -v sharedvolume:/data nginx
  9. 数据卷容器

    创建专门用于共享数据的容器,其他容器通过--volumes-from继承其数据卷:

    1
    2
    3
    4
    5
    6
    # 创建数据卷容器
    docker create --name datavolume -v /data busybox

    # 其他容器继承数据卷
    docker run -d --name app1 --volumes-from datavolume nginx
    docker run -d --name app2 --volumes-from datavolume nginx
  10. 数据卷备份与恢复

    1
    2
    3
    4
    5
    # 备份数据卷到宿主机文件
    docker run --rm -v myvolume:/source -v $(pwd):/backup busybox tar -czvf /backup/volume_backup.tar.gz -C /source .

    # 从备份恢复数据卷
    docker run --rm -v myvolume:/target -v $(pwd):/backup busybox sh -c "tar -xzvf /backup/volume_backup.tar.gz -C /target"

容器卷 ro 和 rw 规则

Docker 支持两种主要挂载类型:

  • 数据卷(Volumes):由 Docker 管理,存储在 /var/lib/docker/volumes/ 目录下
  • 绑定挂载(Bind Mounts):直接挂载宿主机的任意目录

无论哪种挂载类型,都可以通过 :ro:rw 参数控制容器对挂载点的访问权限。

  • :ro(Read-Only) 容器只能读取挂载点的数据,无法写入或修改。如果尝试写入,会抛出 Read-only file system 错误。 示例-v myvolume:/data:ro-v /host/path:/data:ro
  • :rw(Read-Write,默认值) 容器可以自由读写挂载点的数据。如果不指定权限,默认为 rw示例-v myvolume:/data(等同于 -v myvolume:/data:rw

权限控制是有范围的,权限仅限制容器内对挂载点的访问,不影响宿主机对原始数据的操作:

  • 即使容器以 :ro 挂载,宿主机仍可自由读写数据卷或绑定目录
  • 宿主机对数据的修改会实时反映到容器内(无论 ro 还是 rw
挂载类型 :ro 效果 :rw 效果
数据卷(Volumes) 容器无法修改 /var/lib/docker/volumes/... 中的数据 容器可修改数据卷中的数据
绑定挂载(Bind Mounts) 容器无法修改宿主机目录中的数据 容器可修改宿主机目录中的数据

根据上面我们的测试,可以总结出来一个只读和读写的挂载测试

测试只读挂载

1
2
3
4
5
6
7
8
9
# 创建数据卷
docker volume create myvolume

# 启动容器,只读挂载
docker run -d --name test_ro -v myvolume:/data:ro nginx

# 进入容器尝试写入
docker exec -it test_ro bash
echo "test" > /data/test.txt # 会报错:Read-only file system

测试读写挂载

1
2
3
4
5
6
7
8
9
10
11
# 启动容器,读写挂载(省略 :rw)
docker run -d --name test_rw -v myvolume:/data nginx

# 进入容器写入
docker exec -it test_rw bash
echo "test" > /data/test.txt # 成功写入
exit

# 宿主机验证文件存在
docker volume inspect myvolume # 找到 Mountpoint
cat /var/lib/docker/volumes/myvolume/_data/test.txt # 输出 "test"

绑定挂载的权限控制

1
2
3
4
5
6
7
8
9
10
11
# 创建宿主机目录
mkdir -p ~/host_data

# 只读绑定挂载
docker run -it --rm -v ~/host_data:/data:ro alpine sh
echo "test" > /data/test.txt # 报错:Read-only file system

# 读写绑定挂载
docker run -it --rm -v ~/host_data:/data alpine sh
echo "test" > /data/test.txt # 成功写入
ls /data # 显示 test.txt

对于绑定挂载,如果宿主机目录本身权限限制严格(如只读),即使容器以 :rw 挂载,也无法写入。

1
2
3
4
5
6
# 宿主机创建只读目录
mkdir -p ~/readonly && chmod 555 ~/readonly

# 容器以 rw 挂载,但仍无法写入
docker run -it --rm -v ~/readonly:/data alpine sh
touch /data/test.txt # 报错:Permission denied

而且无法直接修改已运行容器的挂载权限,必须停止并重新创建容器。例如:

1
2
3
4
5
6
# 错误做法:修改已运行容器的挂载权限
docker exec -it test_ro bash # 无法在容器运行时更改 /data 的权限

# 正确做法:停止容器,重新创建
docker stop test_ro && docker rm test_ro
docker run -d --name test_ro_new -v myvolume:/data:rw nginx # 改为 rw

容器数据卷的继承和共享

在 Docker 中,容器卷的继承共享是实现数据持久化和容器间协作的核心机制。

卷继承

卷继承是指新容器通过挂载已有容器的数据卷来获取其数据。这种机制允许容器间共享数据,即使原始容器已停止或删除。

如何实现卷继承,使用 --volumes-from 参数,语法

1
docker run -d --name 新容器名 --volumes-from 已有容器名 镜像名

继承规则详解

  • 新容器会继承源容器的所有挂载卷(包括数据卷和绑定挂载),且权限保持一致。
1
2
3
4
5
# 创建源容器并挂载卷
docker run -d --name source_container -v myvolume:/data nginx

# 新容器继承卷
docker run -d --name new_container --volumes-from source_container alpine
  • 多层继承 卷继承支持链式传递,例如:

    1
    A --volumes-from→ B --volumes-from→ C

    此时,A 会继承 B 和 C 的所有卷。

  • 只读 / 读写权限继承 新容器无法修改继承卷的读写权限,必须与源容器一致。例如:

    1
    2
    3
    4
    5
    # 源容器以只读挂载
    docker run -d --name source -v myvolume:/data:ro nginx

    # 继承容器也只能只读访问
    docker run -it --name inherit --volumes-from source alpine sh

来实际操作理解一下卷继承,粗略的步骤大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 创建源容器并写入数据
docker run -it --name source_container -v myvolume:/data alpine sh
echo "Hello from source" > /data/test.txt
exit

# 2. 创建继承容器(源容器可停止或运行)
docker run -it --name inherit_container --volumes-from source_container alpine sh
cat /data/test.txt # 输出: Hello from source
exit

# 3. 删除源容器,继承容器仍可访问数据
docker rm -f source_container
docker start inherit_container
docker exec inherit_container cat /data/test.txt # 依然能访问

在这里以卷继承为例子,做一个完整的实验

先做一些环境准备,绑定一下挂载目录

1
2
3
4
5
6
7
8
# 创建测试目录(用于绑定挂载演示)
mkdir -p ~/docker-test/host-data

# 创建命名数据卷
docker volume create test-volume

# 确认卷创建成功
docker volume ls
image-20250715111903760

然后创建源容器并且写入数据

创建容器然后将命名卷挂载到容器的 /data/volume 目录,将宿主机目录挂载到容器的 /data/host 目录。from-source.txt 文件分别写入到命名卷和绑定挂载目录中。命名卷的数据存储在 Docker 内部,绑定挂载的数据直接存储在宿主机。验证ls后,容器内 ls 命令应显示两个文件,表明挂载和写入成功。

1
2
3
4
5
6
7
8
# 创建源容器并挂载卷(同时使用命名卷和绑定挂载)
docker run -it --name source-container -v test-volume:/data/volume -v ~/docker-test/host-data:/data/host alpine sh

# 在容器内执行以下命令(进入容器后)
echo "Volume Data" > /data/volume/from-source.txt
echo "Host Data" > /data/host/from-source.txt
ls -l /data/volume /data/host # 查看文件
exit # 退出容器
image-20250715112138623

然后通过卷继承创建新容器

--volumes-from source-containerinherit-container 继承 source-container 的所有卷配置(包括命名卷和绑定挂载)。

  • 继承容器可以访问源容器写入的数据(from-source.txt)。
  • 继承容器写入的新数据(from-inherit.txt)也会同步到相同卷。

验证:ls 显示两个目录下均有原有文件和新文件。cat 命令确认文件内容一致。

1
2
3
4
5
6
7
8
9
10
11
12
# 创建继承容器(不直接挂载卷,而是继承 source-container 的卷)
docker run -it --name inherit-container --volumes-from source-container alpine sh

# 在继承容器内验证数据(进入容器后)
ls -l /data/volume /data/host # 应看到与源容器相同的文件
cat /data/volume/from-source.txt # 输出: Volume Data
cat /data/host/from-source.txt # 输出: Host Data

# 在继承容器内写入新数据
echo "New Volume Data" > /data/volume/from-inherit.txt
echo "New Host Data" > /data/host/from-inherit.txt
exit # 退出容器
image-20250715112317447

验证卷继承的独立性

可以看到,继承容器能访问源容器的数据,即使源容器已删除,而且继承容器写入的数据已经持久化

inherit-container 仍然可以访问卷中的数据,证明卷与容器生命周期解耦。宿主机目录 ~/docker-test/host-data 中的文件与容器内一致,说明绑定挂载直接操作宿主机文件。

1
2
3
4
5
6
7
8
9
10
# 停止并删除源容器(模拟故障或维护)
docker stop source-container
docker rm source-container

# 启动继承容器(验证数据仍然可用)
docker start inherit-container
docker exec inherit-container ls -l /data/volume /data/host

# 在宿主机验证绑定挂载的数据
ls -l ~/docker-test/host-data # 应看到 from-source.txt 和 from-inherit.txt
image-20250715112425730

清理实验的数据

1
2
3
4
5
6
7
8
9
# 停止并删除所有容器
docker stop inherit-container inherit-container2
docker rm inherit-container inherit-container2

# 删除命名数据卷
docker volume rm test-volume

# 删除宿主机测试目录
rm -rf ~/docker-test

卷共享

卷共享是指多个容器直接挂载同一个数据卷,实现数据实时同步。与继承不同,共享不需要依赖特定容器。

如何实现卷共享?

  • 命名数据卷共享 直接通过卷名挂载:

    1
    2
    3
    4
    5
    6
    7
    8
    # 创建命名卷
    docker volume create shared_data

    # 容器1挂载
    docker run -d --name container1 -v shared_data:/data nginx

    # 容器2挂载同一个卷
    docker run -d --name container2 -v shared_data:/data mysql
  • 绑定挂载共享 多个容器挂载宿主机同一目录:

    1
    2
    3
    4
    5
    # 容器1绑定挂载
    docker run -d --name container1 -v /host/path:/data nginx

    # 容器2绑定相同目录
    docker run -d --name container2 -v /host/path:/data mysql
  • 共享卷会出现读写冲突,当多个容器以 rw(读写)模式挂载同一卷时:

    • 文件覆盖风险:多个容器同时修改同一文件可能导致数据丢失
    • 写入顺序问题:写入操作的顺序不确定,可能导致数据不一致
    • 建议对共享卷进行分区(如容器 1 写 /data/logs,容器 2 写 /data/config

来实际操作理解一下卷共享,粗略的步骤大概如下

1
2
3
4
5
6
7
8
9
# 1. 创建共享卷
docker volume create shared_logs

# 2. 容器1写入日志
docker run -d --name logger -v shared_logs:/var/log nginx

# 3. 容器2监控日志(只读挂载)
docker run -it --name monitor -v shared_logs:/logs:ro alpine sh
tail -f /logs/access.log # 实时查看nginx日志