容器的文件系统是临时的——docker rm 时容器层全部销毁。任何需要持久化的数据,都必须挂载到 Volume 或 bind mount。这一篇把 Volume 的生命周期、Copy-on-Write 机制、跨主机挂载、共享卷在容器间传文件、清理策略一次性收齐。
阅读对象:需要把数据库 / 上传文件 / 配置中心等状态放到容器里的开发者、运维同学
覆盖范围:volumes vs bind mounts vs tmpfs + 命名卷操作 + Copy-on-Write + docker-compose 卷写法 + NFS / SSHFS / vieux 跨主机 + 共享卷传文件 + 清理策略
一、为什么需要 Volume
容器分层结构:
- 镜像层(Image Layer):只读、共享,多个容器可共用
- 容器层(Container Layer):读写、临时,容器销毁就消失
如果数据写在容器层,docker rm 那一刻数据就没了。Volume 就是把数据"挂"到宿主机文件系统上,绕开容器层,让数据活过容器生命周期。
二、三种挂载方式
Docker 1.9+(2015-11 GA)正式引入了 named volume(命名卷),与 bind mount、tmpfs 并列为三种挂载类型:
| 类型 | 路径 | 持久化 | 性能 | 适用 |
|---|
| Volume | Docker 管理(默认 /var/lib/docker/volumes/) | 是 | 优 | 数据库、上传文件、共享数据 |
| bind mount | 任意宿主机路径 | 是 | 优 | 配置文件、特殊路径、debug |
| tmpfs | 宿主机内存 | 否(重启丢) | 极优 | 敏感数据、临时缓存 |
When to use:
- Volume(推荐):数据库、长期存储——Docker 帮你管理路径、迁移、备份
- bind mount:配置文件、日志、需要直接编辑宿主机文件的场景
- tmpfs:密码、token、不想落盘的临时缓存
三、Volume 操作
3.1 命名卷的生命周期
1
2
3
4
5
6
7
8
| # 创建(指定名字)
docker volume create data_volume
# 列出
docker volume ls
# 看详情
docker volume inspect data_volume
|
inspect 输出:
1
2
3
4
5
6
7
8
9
10
| [
{
"CreatedAt": "2022-07-28T11:02:14+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/data_volume/_data",
"Name": "data_volume",
"Scope": "local"
}
]
|
关键字段:
Driver: local:默认驱动,只能用于当前 Docker 主机上的容器Scope: local:本地范围(要跨主机挂载需要用其他 driver)Mountpoint:数据在宿主机上的实际位置
3.2 隐式创建(run 时指定卷名)
如果不显式 docker volume create,docker run -v name:/path 会自动创建:
1
| docker run -it --rm --name nginx -p 8080:80 -v demo-earthly:/usr/share/nginx/html nginx
|
3.3 Dockerfile 中声明
1
2
3
4
| FROM nginx:latest
RUN echo "<h1>Hello from Volume</h1>" > /usr/share/nginx/html/index.html
VOLUME /usr/share/nginx/html
|
构建并运行:
1
2
3
4
| docker build -t demo-earthly .
docker run -p 8080:80 demo-earthly
docker volume ls
# 输出随机卷名:20879e3f0bfaf0eed63cb7f37c4b9545084a703f888a230b8aedc2082c836281
|
每次启动新容器都会创建新卷(匿名卷),内容是 /usr/share/nginx/html。
3.4 删除
1
2
3
4
5
6
7
8
| # 删指定卷
docker volume rm data_volume
# 删所有未挂载的卷
docker volume prune
# 删 dangling 镜像 + 未挂载卷(危险)
docker system prune -af --volumes
|
重要约束:docker volume rm 不能删正在被容器或服务使用的卷。docker volume prune 默认不会删正在使用的卷,但加 -f 也不会绕过这个保护——保护在 daemon 层。
四、Copy-on-Write 机制
docker build 时每条指令构建一层,只读。docker run 时构建一个读写容器层。
修改容器内某个文件时:
- 如果该文件在镜像层(只读),Docker 会从镜像层"复制"一份到容器层
- 容器层是读写层,修改只在容器层生效
- 容器销毁,容器层消失,修改也没了——这就是为什么需要 Volume
典型场景:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 容器内
echo "new content" > /etc/nginx/nginx.conf
# 容器外看
docker exec -it <container> cat /etc/nginx/nginx.conf
# 输出 "new content"
# 删容器
docker rm -f <container>
# 启动新容器
docker run -d nginx
# 新容器内的 /etc/nginx/nginx.conf 是默认的
# 原因:刚才的"new content"在容器层里,随容器销毁而消失
|
重要结论:容器层的修改不能跨容器共享。要让数据持久 + 跨容器共享,必须 Volume / bind mount。
五、bind mount vs Volume
5.1 语法对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # Volume(短语法)
docker run -d -v mydata:/var/lib/mysql mysql:5.7
# bind mount(短语法)
docker run -d -v /host/path:/container/path nginx
# 长语法(--mount)— 更显式
docker run -d \
--mount type=volume,source=mydata,target=/var/lib/mysql \
mysql:5.7
docker run -d \
--mount type=bind,source=/host/path,target=/container/path \
nginx
|
5.2 长语法参数说明
| 参数 | 含义 |
|---|
type | volume / bind / tmpfs |
source / src | 卷名 / 宿主机路径 |
destination / dst / target | 容器内路径 |
readonly / ro | 是否只读挂载 |
volume-opt | 额外 mount 选项(可多次) |
5.3 实战:初始化数据到 Volume
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 创建两个卷
docker volume create test_nginx_html
docker volume create test_nginx_conf
# 准备数据到宿主机
sudo cp -r /home/nginx/html/* /var/lib/docker/volumes/test_nginx_html/_data
sudo cp /home/nginx/conf/my_custom.conf /var/lib/docker/volumes/test_nginx_conf/_data
# 启动容器,volume 自动挂载
docker run -d \
--restart=always \
-p 8080:8080 \
--name test_nginx \
--mount src=test_nginx_html,dst=/app \
--mount src=test_nginx_conf,dst=/opt/bitnami/nginx/conf/server_blocks \
bitnami/nginx:latest
|
Why 直接 cp 到 _data:volume 在宿主机上的真实路径是 /var/lib/docker/volumes/<name>/_data,容器启动前预先填数据是常见模式。也可以用 docker cp 复制到运行中的容器目录,但已挂载的 volume 路径会变。
六、docker-compose 里的卷
6.1 bind mount 写法
1
2
3
4
5
6
7
8
| version: "3.2"
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./target:/usr/share/nginx/html
|
6.2 命名卷 + 多容器共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| version: "3.2"
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- html_files:/usr/share/nginx/html
web1:
image: nginx:latest
ports:
- "8081:80"
volumes:
- html_files:/usr/share/nginx/html
volumes:
html_files:
|
docker compose up 时自动创建 <project_name>_html_files 命名卷,html_files 在 web 和 web1 之间共享。
6.3 引用已存在的卷
1
2
3
| volumes:
html_files:
external: true
|
要先用 docker volume create html_files 创建,compose 才不会报错说找不到。
七、跨主机挂载:NFS
默认的 local driver 只能在当前主机用。Docker 1.9+ 内置支持 NFS 挂载(用 local driver 即可),不需要装额外插件:
1
2
3
4
5
6
7
8
9
10
| # 安装 NFS 客户端
apt install nfs-common # Debian/Ubuntu
yum install -y nfs-utils # CentOS/RHEL
# 创建 NFS 卷
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=10.0.0.10,rw \
--opt device=:/path/to/dir \
nfs_volume
|
挂载到容器:
1
2
3
4
| docker run -d -it \
--name myapp \
--mount source=nfs_volume,target=/data \
myapp:latest
|
docker-compose 写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| version: "3.2"
services:
myapp:
image: myapp:latest
ports:
- "8080:8080"
volumes:
- type: volume
source: nfs_volume
target: /data
volume:
nocopy: true
volumes:
nfs_volume:
driver_opts:
type: "nfs"
o: "addr=10.0.0.10,nolock,soft,rw"
device: ":/path/to/dir"
|
Why 推荐 nolock,soft:网络抖动时 hard 会卡住进程、nolock 减少锁竞争、soft 失败会返回错误而不是 hang。
八、用 volume driver 把数据存到其他主机
Docker Volume Plugins 生态丰富:vieux/sshfs(已停更)、Azure File Storage、AWS EFS、S3 driver、GlusterFS、Ceph、IPFS…
重要:vieux/sshfs plugin 已经停更,生产不要用——新的容器场景推荐自建 NFS 或云厂商文件存储。
历史 demo(演示用):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 安装 sshfs 插件
docker plugin install --grant-all-permissions vieux/sshfs
# 用 sshfs 创建卷
docker volume create --driver vieux/sshfs \
-o sshcmd=user@10.32.2.134:/home/user/sshvolume \
-o password=yourpassword \
mysshvolume
# 挂载到容器
docker run -id \
--name testcon \
--mount type=volume,volume-driver=vieux/sshfs,source=mysshvolume,target=/world \
ubuntu /bin/bash
|
九、共享卷在容器间复制文件
Volume 的天然优势:多容器可同时挂载。可以用这个特性在容器间传文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 创建共享卷
docker volume create shared_data
# 2. 容器 A 写文件到卷
docker run -it --name=writer \
--mount source=shared_data,destination=/data \
ubuntu bash
# 在容器内
root@ded392c589ea:/# touch /data/demo.txt
# 3. 容器 B 读同一卷
docker run -it --name=reader \
--mount source=shared_data,destination=/data \
ubuntu bash
# 在容器内
root@feef37293ea5:/# ls /data
# 输出:demo.txt
|
典型场景:CI 流水线里"build 容器产出 artifacts → 测试容器拿同一份 artifacts 跑测试",共享卷是干净的解法。
十、清理无用的卷
1
2
3
4
5
6
7
8
9
10
11
| # 只删 dangling 卷
docker volume ls -f "dangling=true" -q | xargs --no-run-if-empty docker volume rm
# 一键清(删所有未挂载的卷 + dangling 镜像 + 停止的容器)
docker system prune -af --volumes
# 找出最大的卷 / 目录,定位"占空间元凶"
du -sh * | sort -hr | head
# 清理 2 天前的文件
find ./ -mtime +1 | xargs rm -rf
|
十一、要点回顾
- Volume / bind mount / tmpfs 三选一,数据库用 Volume、配置用 bind mount、敏感数据用 tmpfs
- 容器层是临时的——任何持久化数据必须挂到卷或宿主机目录
- Copy-on-Write决定了"修改容器内文件 ≠ 改镜像"——重启后默认文件
docker volume rm 不能删正在使用的卷——保护在 daemon 层- NFS 跨主机用内置
local driver 即可,不需要装额外插件 - 共享卷传文件是多容器协作的"原始"模式
vieux/sshfs 已停更——生产场景用云厂商文件存储或自建 NFS
十二、小结
Volume 是 Docker 持久化的"地基"。理解了:
- 三种挂载类型——volume / bind mount / tmpfs 的取舍
- Copy-on-Write——容器层为什么不能持久化
- NFS 跨主机——单机 → 多机的进阶
- 共享卷传文件——容器间协作的底层模式
剩下的就是"挑对的存储后端":本地 SSD 给数据库、NFS / EFS / Ceph 给共享文件、S3 兼容给对象存储。
下一步:理解了单机 Volume,下一步是 Kubernetes 的 PV / PVC / StorageClass——把"申请存储"变成声明式 API,配合 CSI 驱动对接各种后端(NFS / Ceph / 云盘)。K8s 的存储抽象在 1.21+ 进入 GA,是云原生存储的事实标准。
参考资料