CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Docker 容器入门/课时/数据卷与挂载
RawBlame
课时目录12 节

Docker 容器入门

  1. 01容器是什么
  2. 02安装与环境
  3. 03Docker CLI 基础
  4. 04镜像与分层
  5. 05编写 Dockerfile
  6. 06构建与优化
  7. 07镜像仓库
  8. 08数据卷与挂载
  9. 09网络
  10. 10Docker Compose
  11. 11容器安全基础
  12. 12综合练习与 K8s 方向

数据卷与挂载

volume、bind mount、MySQL 持久化。

30 分钟

数据卷与挂载

这一课你将能做到

  1. 亲眼复现「删容器丢数据」的事故,再用 named volume 修复,让 MySQL 数据在容器删除重建后完好如初
  2. 用 bind mount 把 MySQL 配置文件、Spring Boot 日志目录挂到宿主机,实现「容器外可查可改」
  3. 掌握数据备份与恢复的通用命令,并建立「什么数据用什么挂载方式」的决策习惯

为什么要学

这是整个 Docker 学习曲线里最经典的事故现场:数据库容器跑得好好的,某天升级版本 docker rm 掉旧容器再起新的——用户数据全没了。第 4 课已经从原理上解释过(可写层随容器销毁),本课给出工程解法:数据卷(Volume)。

对 Spring Boot 后端来说,需要持久化的东西不止 MySQL:Redis 的 AOF/RDB 文件、应用日志、上传的文件、MySQL 自定义配置——它们分别适合 named volume、bind mount 等不同方式。选错方式不会马上报错,但会在某天变成丢数据事故或权限地狱。

先建立图像

三种挂载方式,一条 -v 参数三种长相:

-v mysql-data:/var/lib/mysql     ← ① named volume:Docker 统一管理(推荐存数据)
-v H:/work/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro
                                 ← ② bind mount:宿主机路径直挂(推荐放配置)
-v /dev/shm:/tmpfs               ← ③ tmpfs:内存挂载(敏感临时数据,停即没)

              宿主机
┌─────────────────────────────────────┐
│  Docker 管理区            你的目录    │
│  /var/lib/docker/volumes/  H:/work  │
│       ▲                      ▲      │
│       │ named volume         │ bind │
│  ┌────┴──────────────────────┴───┐  │
│  │        MySQL 容器             │  │
│  │  /var/lib/mysql  /etc/mysql   │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

关键差异(高频面试点):

named volumebind mount
路径来源Docker 管理(Linux 在 /var/lib/docker/volumes/)宿主机任意路径
首次挂载会把镜像内已有内容拷入 volume直接遮盖容器内目录(空目录也会遮)
可移植性高(与宿主机路径解耦)依赖宿主机路径存在
典型用途MySQL/Redis 数据配置文件、开发时代码热挂载

跟着做

步骤 1:先复现一次「删容器丢数据」

docker run -d --name bad-mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
# 等 30 秒左右让 MySQL 初始化完成,然后写入数据
docker exec bad-mysql mysql -uroot -p123456 -e "CREATE DATABASE demo; USE demo; CREATE TABLE t(id INT); INSERT INTO t VALUES (1);"
docker rm -f bad-mysql

# 重新起一个同名容器
docker run -d --name bad-mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
docker exec bad-mysql mysql -uroot -p123456 -e "SELECT * FROM demo.t;"
# 报错:Table 'demo.t' doesn't exist —— 数据没了

步骤 2:用 named volume 修复

docker volume create mysql-data

docker run -d --name good-mysql \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0

# 初始化完成后写入数据
docker exec good-mysql mysql -uroot -p123456 -e "CREATE DATABASE demo; USE demo; CREATE TABLE t(id INT); INSERT INTO t VALUES (42);"

# 删除容器,卷不动
docker rm -f good-mysql

# 新容器挂同一个卷
docker run -d --name good-mysql2 \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0

docker exec good-mysql2 mysql -uroot -p123456 -e "SELECT * FROM demo.t;"
# 输出 42 —— 数据完好

步骤 3:熟悉 volume 命令族

docker volume ls                       # 列出所有卷
docker volume inspect mysql-data       # 查看卷详情(Mountpoint 是宿主机实际位置)
docker volume rm mysql-data            # 删除卷(有容器使用会拒绝)
docker volume prune                    # 清理未被使用的匿名卷(慎用,先 ls 确认)

步骤 4:bind mount 挂 MySQL 自定义配置

新建 H:/work/mysql-conf/my.cnf:

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
max_connections = 500
slow_query_log = ON
long_query_time = 1

挂载启动(Windows PowerShell 下路径用正斜杠最稳):

docker run -d --name conf-mysql \
  -v mysql-data:/var/lib/mysql \
  -v H:/work/mysql-conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0

docker exec conf-mysql mysql -uroot -p123456 -e "SHOW VARIABLES LIKE 'max_connections';"
# 输出 500 —— 配置生效,且文件带 :ro 只读保护

改配置 = 改宿主机文件 + docker restart conf-mysql,完全不用动镜像。

步骤 5:把 Spring Boot 日志挂出来

假设你的应用写 ./logs/app.log(logback 配置 LOG_PATH 环境变量可控制),镜像里日志在 /app/logs:

docker run -d --name demo-log \
  -v app-logs:/app/logs \
  -e LOG_PATH=/app/logs/ \
  demo-app:1.0

# 查看卷在宿主机的实际位置(Linux 服务器)
docker volume inspect app-logs --format '{{.Mountpoint}}'
# 直接 tail 那个目录下的日志文件

开发环境也可以用 bind mount 直挂到项目目录,方便 IDE 插件实时查看。

步骤 6:数据备份与恢复

named volume 的备份是通用套路:起一个临时容器,同时挂卷和宿主机目录,用 tar 打包。

# 备份:把 mysql-data 卷打包到当前目录(PowerShell 用 ${PWD})
docker run --rm \
  -v mysql-data:/source:ro \
  -v ${PWD}:/backup \
  alpine tar czf /backup/mysql-data-backup.tar.gz -C /source .

# 恢复:反向解包到一个新卷
docker volume create mysql-data-restored
docker run --rm \
  -v mysql-data-restored:/target \
  -v ${PWD}:/backup \
  alpine tar xzf /backup/mysql-data-backup.tar.gz -C /target

# 用恢复的卷起容器验证
docker run -d --name restored-mysql \
  -v mysql-data-restored:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

生产更常用的逻辑备份是 mysqldump(跨版本更稳):docker exec good-mysql mysqldump -uroot -p123456 --all-databases > backup.sql。

讲清楚要点

  1. 可写层与卷的本质区别:可写层属于单个容器、随容器销毁;卷是 Docker 独立管理的对象,生命周期独立于容器。有状态数据一律进卷。
  2. named volume 首次挂载会「填充」:如果挂载点在镜像里已有文件(如 /var/lib/mysql 初始结构),Docker 会把它们拷进空卷——这保证了全新 MySQL 容器能正常初始化。bind mount 没有这个行为,是直接遮盖。
  3. bind mount 的遮盖陷阱:把宿主机空目录挂到 /var/lib/mysql,MySQL 容器会以为数据目录被清空,可能报错或重新初始化。原则:数据用 volume,配置单文件用 bind mount。
  4. :ro 只读挂载是低成本高收益的安全习惯:配置文件容器只该读不该写;误操作写坏配置文件的事故因此绝迹。
  5. Windows bind mount 的两个坑:路径分隔符用 /(H:/work/conf)而不是反斜杠;跨文件系统(NTFS ↔ WSL2 ext4)IO 性能明显低于原生——高频读写的目录优先放 volume,而不是挂载 NTFS 目录。
  6. 备份三选一:tar 卷(物理备份,快但依赖同版本);mysqldump(逻辑备份,跨版本迁移首选);文件系统/云盘快照(整卷定期快照)。选哪种取决于 RTO/RPO 要求。

你可能会懵的地方

  • named volume 的数据到底在宿主机哪里? Linux:/var/lib/docker/volumes/<卷名>/_data。Docker Desktop(Win/Mac):在 WSL2/虚拟机内部,不直接暴露到你的文件系统——这正是推荐用 tar 方式备份的原因。

  • -v 后面到底是卷名还是路径,怎么区分? 看第一段:不含 / 就是卷名(mysql-data:...),含 / 就是 bind mount(H:/work/... 或 /opt/data:...)。新语法 --mount type=volume,src=mysql-data,dst=/var/lib/mysql 更显式,不易歧义。

  • MySQL 容器反复重启/立刻退出,怀疑是卷的问题? 先 docker logs 看报错。两个高频原因:① 卷里残留了旧版本的数据目录,与新版 MySQL 不兼容(清卷重试);② 卷目录属主不对(Linux 上 MySQL 容器内进程是 uid 999):docker run --rm -v mysql-data:/data alpine chown -R 999:999 /data。

  • 匿名卷是什么? docker run -v /var/lib/mysql 不给卷名,Docker 自动生成一串哈希命名。容器删除时匿名卷容易沦为「无人认领的垃圾」,所以规范做法永远写显式卷名。

  • docker compose down 会删卷吗? 默认不会;但 down -v 会删掉 compose 文件里声明的卷——生产环境敲这个命令前请三读。

错法对照

错法问题正解
数据只存容器可写层rm 容器数据即丢-v 卷名:/var/lib/mysql
bind mount 挂数据库数据目录依赖宿主机路径 + 权限难管 + 遮盖陷阱数据用 named volume
配置挂载忘了 :ro容器可能写坏配置文件-v H:/work/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro
Windows 上 bind mount 用反斜杠路径解析失败或挂错位置用正斜杠 H:/work/conf
docker volume prune 乱敲误删未挂载但有价值的卷先 volume ls 逐个确认
备份只拷贝 volume 目录Desktop 场景拿不到路径临时容器 + tar 打包,或 mysqldump

卡点急救

  1. 挂载后容器内看不到宿主机文件:检查路径是否存在、大小写是否一致、Windows 是否用了正斜杠;Docker Desktop → Settings → Resources → File Sharing 确认盘符已共享。
  2. MySQL 起不来报权限错误(Linux):卷目录属主与容器内用户(uid 999)不符。临时容器 chown -R 999:999 修复。
  3. 换了新容器数据「看起来丢了」:确认新容器挂的是不是同一个卷名(docker inspect 新容器 看 Mounts)。常见错误是新容器忘了写 -v。
  4. 恢复备份后 MySQL 版本不兼容:物理备份跨大版本恢复会报错。改用 mysqldump 逻辑备份,或先用同版本容器恢复再升级。
  5. 磁盘被 volume 吃满:docker system df -v 查看卷占用;确认无用的卷 volume rm,应用侧检查日志是否无限增长。

面试常问

  • volume 和 bind mount 的区别?(必考) volume 由 Docker 管理与定位(可移植、支持卷驱动、首次挂载填充镜像内容),适合数据库等纯数据;bind mount 直接映射宿主机路径(依赖路径、可被容器写宿主机),适合配置文件与开发热挂载。敏感临时数据用 tmpfs。

  • 容器化的 MySQL 如何保证数据不丢? 三层:named volume 持久化数据目录;定期备份(mysqldump 逻辑备份 + 卷快照);升级/迁移时先备份再操作,回滚依赖备份卷。

  • 为什么数据库容器的数据不能放在容器内? 容器可写层随容器销毁,且可写层 IO 性能不如卷、无法被其他容器共享、备份困难。有状态服务容器化的前提就是存储与计算分离。

  • 怎么备份一个 Docker volume? 标准套路:docker run --rm -v 数据卷:/source:ro -v 宿主机目录:/backup alpine tar czf /backup/x.tar.gz -C /source .。原理是借助临时容器把卷内容写到宿主机文件系统。

自测三问

  1. -v mysql-data:/var/lib/mysql 和 -v H:/data/mysql:/var/lib/mysql 分别是哪种挂载?首次挂载行为有什么不同?
  2. 写出备份 mysql-data 卷的完整命令,并解释每个 -v 的作用。
  3. MySQL 容器起来就退出,日志报权限错误,你会按什么顺序排查?

小结一句话

卷把数据的生命周期从容器中解放出来——数据进 named volume、配置走只读 bind mount、备份用临时容器打包,这三条习惯守住「删容器不丢数据」的底线。

延伸

  • Docker Volumes 官方文档
  • 下一课预告:容器网络——为什么容器里连不上 localhost:3306?自定义网络如何让 Spring Boot 用容器名直连 MySQL。
加载本节练习…
← 上一课镜像仓库下一课 →网络