外观
项目 6:Docker 数据卷、绑定挂载与数据持久化
约 2808 字大约 9 分钟
DockerVolumeBind Mount数据持久化
2026-05-29
项目目标
你在第 3 课已经用 Bind Mount 部署了 2048 小游戏。本课把视角从"前端热更新"切换到"数据库持久化"——公司有一个 MySQL 数据库跑在容器里,如果容器崩溃了,订单数据不能丢。
你将深入理解 Bind Mount 和 Docker Volume 的底层差异,学会为 MySQL 配置持久化存储,为 Nginx 注入配置文件,并通过 Volume 备份实现数据安全。
一、回顾:容器为什么"阅后即焚"?
Docker 容器的文件系统由"只读镜像层 + 可读写层"组成。容器运行期间的一切文件修改都保存在最顶部的可读写层中。一旦容器被 docker rm 删除,这个读写层就灰飞烟灭。
这就是容器的无状态特性。如果你把 MySQL 数据库跑在容器里,不做任何持久化——容器故障重启后,所有客户订单数据就没了。
第 3 课我们已经学了两种挂载的基本概念。本课更进一步:用真实的生产场景来对比它们的差异。
课堂速记图
这张图把数据持久化挑战、Volume 与 Bind Mount 的差异、数据卷使用流程、容器间共享和常见场景放在一页里。建议先看总览,再进入下面的命令实践。

二、Bind Mount vs Docker Volume:深度对比
📁 数据持久化:Bind Mount vs Docker Volume
对比不同挂载模式下,容器销毁对数据生命周期的影响
【启动容器】直接启动 MySQL 容器,不进行任何挂载。
🖥️ 宿主机存储区
📂 用户自定义目录
/Users/bob/data空
🔒 Docker 托管区
/var/lib/docker/volumes/📦 MySQL 容器
读写层 (可丢弃)
空
| 维度 | Bind Mount | Docker Volume |
|---|---|---|
| 数据位置 | 宿主机上你指定的任意路径 | Docker 托管(/var/lib/docker/volumes/) |
| 管理方式 | 你自己管理 | Docker 全权管理 |
| 创建方式 | -v /host/path:/container/path | docker volume create + -v vol-name:/container/path |
| 权限控制 | 宿主机路径权限会直接影响容器读写 | 由 Docker 管理存储位置,权限仍由容器进程和卷内文件决定 |
| 典型场景 | 前端热更新、配置文件注入 | 数据库持久化、跨容器共享 |
| 跨容器共享 | 可以,但需注意路径权限 | 原生支持,多个容器同时挂载同名 Volume |
核心区别一句话:Bind Mount 是"你告诉 Docker 用哪个目录";Volume 是"Docker 告诉你数据存在哪"。
三、场景一:MySQL 数据库持久化(Volume)
3.1 创建 Volume
# docker volume create:向 Docker 申请一块专用的持久化存储空间
docker volume create mysql-data
# 查看 Volume 详情
docker volume inspect mysql-data
# 关注 "Mountpoint" 字段——Docker 帮你选好了宿主机上的存储位置3.2 启动 MySQL 并挂载 Volume
问题:由 Docker 完全托管的数据卷英文名叫什么?(全小写,6个字母)
docker rm -f mysql-db 2>/dev/null || true
docker run -d \
--name mysql-db \
-e MYSQL_ROOT_PASSWORD=secret123 \
-v mysql-data:/var/lib/mysql \
mysql:8.0📌
/var/lib/mysql是 MySQL 在容器内默认存放数据文件的路径。挂载 Volume 后,所有数据实际写入 Docker 托管的空间。
3.3 验证持久化
# 在 MySQL 中创建一条测试数据
sleep 20
docker exec mysql-db mysql -uroot -psecret123 -e "CREATE DATABASE testdb;"
# 故意删除容器(模拟崩溃)
docker rm -f mysql-db
# 重新启动一个新容器,挂载同一个 Volume
docker run -d \
--name mysql-db-new \
-e MYSQL_ROOT_PASSWORD=secret123 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
# 检查数据库是否还在
sleep 20
docker exec mysql-db-new mysql -uroot -psecret123 -e "SHOW DATABASES;"如果能看到 testdb,就证明数据没有随容器一起消失——Volume 的生命周期独立于容器。
四、场景二:Nginx 配置文件注入(Bind Mount)
4.1 准备自定义 Nginx 配置
mkdir -p nginx-config
cat > nginx-config/default.conf << 'EOF'
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /status {
stub_status on; # 开启 Nginx 状态页
allow all; # 课堂演示直接开放,生产环境应限制来源
}
}
EOF4.2 用 Bind Mount 注入单个配置文件
docker run -d \
--name nginx-custom \
-p 8080:80 \
-v "$(pwd)/nginx-config/default.conf:/etc/nginx/conf.d/default.conf" \
nginx:alpineBind Mount 可以精确到文件级别
-v /host/file.conf:/container/file.conf 可以把单个配置文件映射进容器,而不需要挂载整个目录。这对于注入配置文件特别实用。
4.3 验证配置生效
浏览器访问 http://localhost:8080/status(远程实验机使用 http://宿主机IP:8080/status),应该能看到 Active connections 等 Nginx 状态信息。
五、场景三:Volume 备份与迁移
Volume 的数据存在 Docker 托管目录,不建议直接 cp 宿主机上的 /var/lib/docker/volumes/,在 Docker Desktop 等环境中也通常无法直接访问。更稳妥的做法是用临时容器挂载 Volume 后再打包:
🧰 Volume 魔法:如何备份隔离区的数据?
图解使用临时容器(alpine)同时挂载保险箱和宿主机目录进行数据打包的过程
docker run --rm -v mysql-data:/data -v $(pwd):/backup alpine tar czf ...【初始状态】宿主机的当前目录(左侧)与 Docker 托管的 Volume 保险箱(右侧)是完全隔离、互不相通的。
🖥️ 宿主机当前目录
$(pwd)📂 /backup
🔒 Docker 托管区
/var/lib/docker/volumesmysql-data (保险箱)
📄 order.db📄 user.db
# 备份数据库文件前,先停止正在使用该 Volume 的 MySQL 容器,避免文件正在写入
docker stop mysql-db-new
# 备份:启动一个临时容器,挂载 Volume,用 tar 打包数据
docker run --rm \
-v mysql-data:/data \
-v "$(pwd)":/backup \
alpine tar czf /backup/mysql-backup.tar.gz -C /data .
# 迁移到另一台机器:
# 1. 把 mysql-backup.tar.gz 传到目标机器
# 2. 创建同名 Volume
# 3. 用临时容器解压恢复
docker run --rm \
-v mysql-data:/data \
-v "$(pwd)":/backup \
alpine tar xzf /backup/mysql-backup.tar.gz -C /data六、提交物清单
| 文件 | 说明 |
|---|---|
docker volume ls 截图 | 显示 mysql-data Volume |
| MySQL 持久化验证截图 | 删除容器后重建,SHOW DATABASES 仍含 testdb |
| Nginx 状态页截图 | 浏览器访问 /status 后显示 stub_status 结果 |
storage_notes.md | 对比 Volume 和 Bind Mount 的适用场景、优缺点和权限差异 |
mysql-backup.tar.gz | 通过临时容器打包得到的 Volume 备份文件 |
当堂检测
- Bind Mount 和 Docker Volume 的核心区别是什么?各举一个最适合的使用场景。
- 删除 MySQL 容器后,
docker volume ls还能看到 Volume 吗?新建同名容器挂载同一个 Volume,之前的数据库还在吗? - 为什么不建议直接
cpDocker 托管目录?备份 Volume 的正确方法是什么?
七、故障排查
| 现象 | 排查方法 |
|---|---|
| MySQL 容器起不来 | 检查 Volume 挂载路径是否为 /var/lib/mysql |
| Volume 数据丢失 | docker volume ls 确认 Volume 未被 docker volume rm 删除 |
| Bind Mount 不生效 | docker inspect 检查 Mounts → Source 路径是否正确 |
八、深度探索任务
以下任务不强制提交,是拔高性质的进阶练习。每个探索任务都包含完整的操作流程和确认问题。
探索任务 1:比较 Bind Mount 和 Volume 的文件权限
目标:通过实际创建文件,直观对比两种挂载方式的权限差异。
操作步骤:
1. 准备 Bind Mount 目录
mkdir -p /tmp/bind-test
echo "hello from bind mount" > /tmp/bind-test/hello.txt2. 准备 Volume
docker volume create vol-test3. 用 Bind Mount 启动容器
docker run --rm -v /tmp/bind-test:/data alpine ls -la /data4. 用 Volume 启动容器
docker run --rm -v vol-test:/data alpine sh -c "echo 'hello from volume' > /data/hello.txt && ls -la /data"5. 分别检查文件权限
# Bind Mount:直接在宿主机查看
ls -la /tmp/bind-test/
# Volume:通过临时容器查看
docker run --rm -v vol-test:/data alpine ls -la /data6. 对比差异
| 对比项 | Bind Mount 中的文件 | Volume 中的文件 |
|---|---|---|
| 所有者 | 宿主机当前用户(如 uid=1000) | 通常是 root |
| 能否从宿主机直接访问 | 能,直接用 ls 或编辑器 | 不建议直接访问;Docker Desktop 上通常需要通过容器 |
| 删除容器后数据是否保留 | 保留(只要宿主机目录没删) | 保留(Volume 独立于容器) |
确认问题
Bind Mount 和 Volume 创建的文件在所有权上有什么不同?为什么说 Volume 比 Bind Mount 更适合数据库场景?如果 Bind Mount 的宿主机目录被你误删了,数据还能恢复吗?
探索任务 2:Volume 跨容器写入冲突测试
目标:了解多个容器同时挂载同一个 Volume 时的并发行为。
操作步骤:
1. 创建共享 Volume
docker volume create shared-vol2. 启动两个容器,同时挂载同一个 Volume
# 终端 1:容器 A 持续向文件追加内容
docker run -d --name writer-a -v shared-vol:/data alpine \
sh -c 'while true; do echo "A writes at $(date +%s)" >> /data/shared.log; sleep 1; done'
# 终端 2:容器 B 也向同一个文件追加内容
docker run -d --name writer-b -v shared-vol:/data alpine \
sh -c 'while true; do echo "B writes at $(date +%s)" >> /data/shared.log; sleep 1; done'3. 观察写入结果
# 等几秒后,查看共享文件的内容
docker run --rm -v shared-vol:/data alpine cat /data/shared.log你会看到 A 和 B 的日志行交错在一起——两个容器的写入都成功了,但顺序交错不保证。
4. 思考并发风险
这里是简单的追加写入,看起来正常。但如果两个容器同时写数据库文件(如 MySQL 的 ibdata1),会怎样?——文件会损坏。
这就是为什么生产环境中不能让多个 MySQL 实例挂载同一个 Volume,除非使用支持共享存储的集群方案。
5. 清理
docker rm -f writer-a writer-b
docker volume rm shared-vol确认问题
两个容器同时向同一个 Volume 中的文件追加写入,数据会丢失吗?如果两个 MySQL 容器挂载同一个 Volume 存储数据文件,可能会发生什么?生产环境中应该如何避免这个问题?
探索任务 3:Volume 备份实战
目标:完整走一遍 Volume 备份→删除→恢复的全流程,理解 Volume 数据独立于容器的生命周期。
操作步骤:
1. 创建测试数据
# 确保 mysql-data Volume 存在(按照第三节步骤创建),并清理前面实验留下的旧 MySQL 容器
docker rm -f mysql-db mysql-db-new mysql-source 2>/dev/null || true
docker run -d --name mysql-source \
-e MYSQL_ROOT_PASSWORD=secret123 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
# 等 MySQL 启动完成后写入测试数据
sleep 10
docker exec mysql-source mysql -uroot -psecret123 -e "
CREATE DATABASE backup_test;
USE backup_test;
CREATE TABLE orders (id INT PRIMARY KEY AUTO_INCREMENT, product VARCHAR(50));
INSERT INTO orders (product) VALUES ('笔记本'), ('键盘');
"2. 备份 Volume
# 停止源容器(可选,但推荐——停服备份更安全)
docker stop mysql-source
# 用临时 alpine 容器打包 Volume 数据
docker run --rm \
-v mysql-data:/data \
-v "$(pwd)":/backup \
alpine tar czf /backup/mysql-data-backup.tar.gz -C /data .检查备份文件是否生成:
ls -lh mysql-data-backup.tar.gz3. 模拟灾难:删除 Volume
docker rm mysql-source
docker volume rm mysql-data
docker volume ls | grep mysql
# 应该看不到 mysql-data 了4. 从备份恢复
# 重新创建同名 Volume
docker volume create mysql-data
# 用临时容器解压恢复
docker run --rm \
-v mysql-data:/data \
-v "$(pwd)":/backup \
alpine tar xzf /backup/mysql-data-backup.tar.gz -C /data
# 启动新的 MySQL 容器挂载恢复的 Volume
docker run -d --name mysql-restored \
-e MYSQL_ROOT_PASSWORD=secret123 \
-v mysql-data:/var/lib/mysql \
mysql:8.05. 验证数据是否完整恢复
sleep 10
docker exec mysql-restored mysql -uroot -psecret123 -e "
SHOW DATABASES;
USE backup_test;
SELECT * FROM orders;
"如果看到 backup_test 数据库和 orders 表里的两条记录("笔记本"、"键盘"),说明备份恢复成功。
6. 清理
docker rm -f mysql-restored
rm mysql-data-backup.tar.gz
# 保留 mysql-data Volume 供后续使用确认问题
为什么备份 Volume 不能直接用 cp 复制宿主机上的 /var/lib/docker/volumes/ 目录?用临时容器做备份的核心原理是什么?如果要定时自动备份 Volume,你会怎么设计?
