第 4 章 数据卷:让数据活过容器
本章目标:理解容器可写层随容器消亡的本质,掌握具名卷、绑定挂载、tmpfs 三种挂载方式及适用场景,学会容器间数据共享与备份恢复。
1. 为什么需要数据卷
容器的一切写入都发生在可写层,而可写层有两个致命属性(第 2、3 章埋的伏笔):
- 容器删除(
docker rm)时可写层一起销毁,数据随之消失; - 可写层与容器绑定,无法跨容器共享,也无法被宿主机直接访问。
于是有了三个需求:持久化(删容器数据不丢)、共享(多容器访问同一份数据)、访问(宿主机直接读写)。Docker 提供了三种挂载方式:
| 方式 | 存储位置 | 生命周期 | 适用场景 |
|---|---|---|---|
| 具名卷(volume) | Docker 管理(Docker Root Dir 下) | 独立于容器,删容器不删卷 | 数据库等需要持久化的数据(首选) |
| 绑定挂载(bind mount) | 宿主机任意目录 | 跟随宿主机文件 | 开发热更新、传配置文件 |
| tmpfs | 内存 | 容器停止即清空 | 敏感临时数据、高速缓存 |
挂载语法(-v 简写与 --mount 详细形式,功能等价,--mount 更明确):
# 具名卷: -v 卷名:/容器内路径 --mount type=volume,source=卷名,target=/路径
# 绑定挂载: -v /宿主绝对路径:/容器内路径 --mount type=bind,source=/宿主路径,target=/路径
# tmpfs: --tmpfs /容器内路径2. 具名卷:数据库持久化的标准答案
卷由 Docker 统一管理,生命周期独立于容器——这是它与 bind mount 最大的区别。
docker volume create data-vol # 创建具名卷
docker volume ls # 列出所有卷
docker volume inspect data-vol # 查看卷的挂载点(宿主机真实路径)
docker volume rm data-vol # 删除卷(先停用使用它的容器)
docker volume prune # 清理所有未被容器使用的卷MySQL 持久化(本章核心示例):
docker run -d --name db \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=app \
-v db-data:/var/lib/mysql \
mysql:8.0流程验证——删容器、重建容器,数据仍在:
docker exec -it db mysql -uroot -psecret -e "CREATE TABLE t(i INT); INSERT INTO t VALUES (1);" 2>/dev/null
docker rm -f db
docker run -d --name db2 \
-e MYSQL_ROOT_PASSWORD=secret \
-v db-data:/var/lib/mysql \
mysql:8.0
docker exec -it db2 mysql -uroot -psecret -e "SELECT * FROM t;" 2>/dev/null # 仍能查到 1
-v db-data:/var/lib/mysql中db-data没有/前缀,Docker 把它识别为卷名(而不是宿主机路径)——这是具名卷与 bind mount 语法上的分水岭。mysql:8.0的官方镜像也声明了 VOLUME/var/lib/mysql:即使你忘了-v,容器也会生成一个匿名卷来保护数据。
匿名卷(-v /容器路径 不写卷名)与具名卷的唯一区别是名字由 Docker 随机生成、难以复用,应尽量显式命名。
3. 绑定挂载:宿主机目录直接进容器
bind mount 把宿主机任意目录直接挂进容器,两边实时互通,最适合开发场景(改代码不用重新构建镜像):
# 把本地 html 目录挂到 nginx 站点根目录
docker run -d --name web -p 8082:80 \
-v "$PWD/html:/usr/share/nginx/html" \
nginx:alpine
# 宿主机改文件,容器立即可见
echo '<h1>hello bind mount</h1>' > html/index.html
curl http://localhost:8082/ # 返回新内容# 传配置文件(如 nginx 配置)
docker run -d --name web -p 8083:80 \
-v "$PWD/nginx.conf:/etc/nginx/conf.d/default.conf:ro" \
nginx:alpine注意三点:
- 路径必须是绝对路径:
-v后不带/会被当成卷名(见第 2 节); - 冒号后
:ro表示只读挂载,防止容器改坏宿主机文件; - 挂载目录会"盖住"容器内原路径的内容:
/usr/share/nginx/html原本的页面会被你挂载的目录整体替换(不会合并)。Windows 上路径形如-v C:\work\html:/usr/share/nginx/html(WSL/Desktop 环境下路径需能被 docker 访问)。
4. tmpfs:活在内存里的临时数据
tmpfs 挂载在内存中,速度快、不落盘,容器停止即清空——适合密码、token 等不想写进磁盘的敏感临时数据,或纯缓存:
docker run --rm -d --name tmp \
--tmpfs /cache \
alpine sleep 300
docker exec tmp sh -c "echo hi > /cache/x && cat /cache/x"
docker restart tmp
docker exec tmp sh -c "cat /cache/x" # 失败:重启后已清空(或改用 no-cache 验证)5. 数据共享与备份
容器间共享卷(如 nginx 与上传服务共享静态资源目录):
docker run -d --name app -v shared:/data alpine sleep 300
docker run -d --name web -v shared:/usr/share/nginx/html nginx:alpine
docker exec app sh -c "echo shared > /data/note.txt"
curl http://localhost:8082/note.txt # nginx 容器里也能读到备份与恢复:卷本质是宿主机上的目录,用 tar 打包即可:
# 备份:把卷内容打包到宿主机
docker run --rm -v db-data:/data -v "$PWD:/backup" alpine \
tar czf /backup/db-data.tar.gz -C /data .
# 恢复:把备份解回卷
docker run --rm -v db-data:/data -v "$PWD:/backup" alpine \
tar xzf /backup/db-data.tar.gz -C /data技巧:借助一个临时的 alpine 容器挂载目标卷完成打包/解包,--rm 用完即弃——这是备份恢复的通用套路。
常见误区
- 误区:删容器会把卷一起删掉。 正确:具名卷独立于容器存在,
docker rm(甚至-f)不会删卷;只有docker volume rm才删。docker rm -v也只会删匿名卷。 - 误区:
-v data:/path会把宿主机data目录挂进去。 正确:没有/前缀的是卷名;要挂宿主机目录必须写绝对路径-v /abs/path:/path。 - 误区:bind mount 与容器内原有文件是"合并"的。 正确:挂载点会整体覆盖容器内原目录内容。
- 误区:容器重启(restart)后 tmpfs 数据还在。 正确:tmpfs 是内存挂载,容器停止/重启即清空,别放需要保留的数据。
小结
- 可写层随容器销毁——需要长期保存的数据必须放卷里。
- 具名卷:Docker 管理、独立生命周期,数据库持久化首选;语法
-v 卷名:/容器路径(无/前缀)。 - 绑定挂载:宿主机目录直挂容器,开发热更新、传配置用;语法
-v /绝对路径:/容器路径[:ro]。 - tmpfs:内存挂载,快但易失,适合敏感临时数据。
- 共享用同一卷挂多个容器;备份用临时 alpine 容器
tar打包。
练习
- 用
mysql:8.0启动数据库并挂载具名卷db-data,建一张表写入数据;删除容器后重建,验证数据仍在(对照第 2 节流程)。 - 创建一个本地目录
html/,用 bind mount 挂到nginx:alpine的站点目录,修改宿主机文件后立即curl验证热更新。 - 用第 5 节方法把某个卷备份成 tar,再恢复到另一个卷中,比较两个卷内容是否一致。
- 思考题:为什么说"数据库容器不挂卷 = 数据随时可能消失"?(提示:回顾
docker rm对可写层的影响。)
可运行示例见
examples/04-data-volumes/,按其中的 README 步骤执行。