我最早用 Docker 的时候栽过一个跟头。当时在某个测试环境里跑了一个数据库容器,往里灌了一批测试数据,后来想换镜像版本,顺手 docker rm 把容器删了,再 docker run 拉起新版本——结果数据全没了。那一刻我才真正意识到,容器本身压根不是用来存放数据的。Docker 数据卷这个概念,就是为了解决这种“容器销毁导致数据丢失”的问题而来的。
这篇文章我从挂载原理讲起,把匿名卷、命名卷、绑定挂载三者的区别彻底理清,然后给出我自己在测试环境里沉淀下来的一套目录挂载规范,再聊两个实战中非常容易踩的坑——权限问题和读写性能问题,最后说下数据卷的备份与迁移。适合刚上手 Docker、对数据持久化还比较模糊的同学,也适合已经用了一阵子、想重新梳理挂载方案的老手。
1. 数据卷解决的是“容器天生不适合存数据”这件事
1.1 可写层与容器生命周期的绑定关系
Docker 镜像是分层构建的,每一层都是只读的。启动容器时,运行引擎会在镜像层之上叠加一个可写层,容器运行期间产生的新文件、修改的文件,都落在这个可写层里。
问题就出在这里:可写层和容器的生命周期是绑在一起的。
容器一旦被删除,可写层也会被清理掉。你写进可写层的所有内容——数据库数据、上传图片、日志文件——全部跟着消失。更麻烦的是,可写层在宿主机上的物理位置由 Docker 自行管理,对你来说是一个不透明的黑盒,你没法直接到宿主机上翻出某个文件来看。就算容器还在正常运行,多个容器之间也无法直接共享彼此的可写层。
用个生活化的类比:镜像像一张打印好的底片,每个容器像从底片冲印出来的照片。你用笔在照片上写的字只存在于这张照片上,底片不会变,照片毁了字也没了。数据卷就像给照片配了一个外接的活页本,运行期间需要保存的内容写到活页本上,换张新照片,字照样还在。
所以数据卷的本质一句话就能说清:把容器内某个路径的存储位置,从容器生命周期管理的可写层,映射到宿主机上独立于容器的目录或块设备中去。数据卷的生命周期可以远超容器,容器删了它还在,等你下次再拉起容器的时再把同一个卷挂进去,数据就回来了。
1.2 镜像内路径、容器内路径与宿主机路径的对应方式
一般文档会把数据卷挂载的写法分成三种,它们的共同点是都包含“容器内路径”这个要素,区别在于“宿主机侧的数据源头是什么”。
第一种是匿名卷,命令里只写容器内路径,例如 docker run -v /data。Docker 会在宿主机上自动创建一块存储区域,但不给你一个好记的名字,后续你想管理它得通过 docker volume ls 去查那一串随机 ID。
第二种是命名卷,例如 docker run -v mydata:/data,冒号前面是你自己起的卷名,Docker 会在统一的卷目录下创建并维护它,你可以用 docker volume create 单独创建、用 docker volume rm 单独删除。
第三种是绑定挂载,例如 docker run -v /home/user/app:/data,冒号前面必须是宿主机上的绝对路径,容器内 /data 目录直接映射到这个宿主机目录上,两边看到的是同一批文件。
很多新手在这里容易糊涂,其实关键在于“镜像内路径”和“容器内路径”常常混在一起讲。镜像里某个目录里本来有哪些文件,你可以用 docker run --rm image ls /data 看一眼,这是镜像内容;但一旦你在运行时挂了个卷上去,容器内路径就被卷接管了。至于接管后原先镜像里的内容还能不能看到,这取决于挂载方式,后面第 2 章会展开说。
1.3 一个容易误解的点:容器重建不等于数据丢失,漏挂卷才是
容器这个对象本身是很廉价的,删了重建很正常。很多人担心“容器删了数据就没了”,其实只要数据放在卷里,容器删了卷还在。真正让数据丢掉的,往往是以下几种操作:
- 用
-v /data建了匿名卷但不知道卷 ID,容器删掉以后匿名卷变成悬空卷,后来被docker volume prune清理掉了; - 换了台机器部署,忘了在新机器上恢复数据卷;
- 挂载点没写对,卷挂了一个位置,程序写的是另一个位置;
- 以为挂载了宿主机目录,实际上因为路径写错,容器内变成了一个新的空目录,程序跑起来用的是这个空目录。
后面三种在实际项目里都遇到过。特别是“漏挂卷”:你把镜像从旧版本升级到新版本,docker run 命令是新写的,结果少写了 -v appdata:/var/lib/app 那一行,容器起来了,数据目录变成镜像里自带的空目录,应用初始化了一遍,旧数据全“看不到了”。乍一看像数据丢了,其实数据都还在卷里,只是这次启动没有挂载进去。
所以我现在有个习惯:任何长期运行的服务,第一步先确认数据卷命令在,第二步才看镜像版本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三种挂载方式的选择:匿名卷、命名卷与绑定挂载
2.1 先拿一张表看清三者各自的适用场景
很多人一上来就在命令行里乱试 -v 参数,我觉得不如先想清楚数据从哪来、由谁写、生命周期多长。我把三种挂载方式放到一起对比:
| 挂载方式 | 典型命令 | 数据物理位置 | 生命周期 | 适合场景 |
|---|---|---|---|---|
| 匿名卷 | docker run -v /data |
Docker 管理的卷目录,ID 随机 | 容器删除后变悬空卷,易被清理 | 临时性数据、本地快速验证、不关心数据具体在哪 |
| 命名卷 | docker run -v mydata:/data |
Docker 管理的卷目录,名称固定 | 独立于容器,显式创建与删除 | 数据库数据、核心业务数据、需要长期保存和跨容器共享的数据 |
| 绑定挂载 | docker run -v /host/path:/data |
宿主机任意指定路径 | 与宿主机目录一致,Docker 不管它 | 开发热更新、日志透传、宿主机直接管理文件 |
从这张表能看出一个规律:数据越重要,越应该让它有名字、生命周期越独立。匿名卷适合“无所谓”的数据,绑定挂载适合“宿主机和容器需要实时互通”的场合。
2.2 绑定挂载的隐藏行为:文件挂载和目录挂载不是一回事
绑定挂载是日常开发中用得最多、也最容易踩坑的挂载方式。
先说一个非常典型的坑:你想把宿主机上的一个配置文件挂载进容器,比如 /root/app/config.yml 挂到 /app/config.yml,但宿主机上这个文件还没创建,你可能以为启动容器时它会自动生成一个空文件。实际上 Docker 的行为是:如果绑定挂载的源路径在宿主机上不存在,且它是文件路径形式,Docker 会直接创建一个目录,然后把这个空目录挂载进去。结果就是容器内 /app/config.yml 变成了一个目录而不是文件,程序读配置的时候报“无法打开 /app/config.yml: is a directory”,排查半天还以为是权限问题。
所以绑定挂载有一个硬性规则:挂文件时,宿主机上的源文件必须先存在;挂目录时,源目录可以不存在但 Docker 会帮你在宿主机上建出来,这一点在很多场合很方便,只是不要把它用在“需要挂文件”的场景。
另一个容易被忽略的点是:绑定挂载的内容不会继承镜像内原有目录里的文件。如果你把一个空目录挂到镜像内一个包含初始数据或默认配置的目录上,容器里看到的将完全是宿主机那个空目录的内容,镜像里的默认文件全部“隐身”了。命名卷则有不同的行为:新创建的命名卷如果为空,挂载到镜像内非空目录时,Docker 会把镜像里该目录的内容复制到卷里,实现“初始化”。这个差异在选挂载方式时非常关键。
2.3 需要共享、需要隔离、需要临时刷写时的选型逻辑
抛开具体命令,我建议你按下面这个决策逻辑来选型:
如果多个容器需要读写同一份数据,用命名卷或绑定挂载都行,把同一个卷或同一个宿主机目录挂给多个容器即可。要注意的是卷本身不提供锁机制,多个进程同时写同一个文件很容易搞坏数据,所以“共享”通常在单写多读场景下才安全。
如果数据只是临时生成、容器重建后可以自动重新生成,没必要持久化,用匿名卷或 tmpfs 就行。匿名卷至少还能残留到宿主机上,tmpfs 干脆只在内存里,容器一停数据立刻消失,适合放临时缓存和编译产物。
如果宿主机上需要直接看文件、改文件,比如开发时代码目录、日志输出目录,用绑定挂载,实时同步,两边看到的完全一样。
如果数据很脏但你必须长期保存,比如数据库的存储目录,优先用命名卷。好处是 Docker 负责管理数据目录,备份恢复都有规范化的命令,删容器也不会误删数据。
另外补充一点:-v 与 --mount 两种写法并存的困惑。-v 语法简洁但有些高级选项表达不了,--mount 用键值对方式更结构化。我个人的习惯是:简单挂载用 -v,涉及只读、tmpfs 大小限制、复杂卷选项时一律用 --mount。
3. 从零设计一套挂载方案:目录约定、Compose 固化与初始化顺序
3.1 先想清楚什么数据需要挂出来,什么数据不需要
很多人在项目里把所有目录都挂出来,结果宿主机上一堆乱七八糟的空目录,管理成本反而更高。从零设计挂载方案的第一步,是明确哪些数据真正需要持久化或共享。
我给团队定的分类标准是这样的:
| 数据类型 | 持久化诉求 | 推荐方案 | 原因 |
|---|---|---|---|
| 数据库数据目录 | 必须持久化 | 命名卷 | 数据重要且路径固定,由 Docker 统一管理备份 |
| 上传文件、附件 | 必须持久化 | 命名卷或宿主机专属目录 | 文件量大,需与宿主机其他服务共享时用绑定挂载 |
| 日志文件 | 需要透出查看 | 绑定挂载到统一日志目录 | 宿主机日志采集、归档、清理都方便 |
| 配置文件 | 需要宿主机控制 | 绑定挂载只读 | 改配置不用重新进容器,只读防止容器内误改 |
| 临时缓存 | 不需要持久化 | tmpfs | 重启丢缓存反而合理,还能绕开磁盘 IO |
| 代码目录(开发期) | 需要实时同步 | 绑定挂载 | 宿主机改代码容器内即时生效 |
| 代码目录(生产期) | 不需要挂载 | 打进镜像 | 减少部署变量,保证镜像一致 |
这条规则看起来简单,但能让目录设计一开始就收敛。我做项目时看到过太多“图省事全部绑定挂载”的例子,最后宿主机上散落着几十个不知道属于哪个服务的目录,清理也不敢清理,部署脚本里路径满天飞。
3.2 用 Compose 文件把挂载配置固化成可复制执行的规范
单容器用 docker run 还行,一旦服务多起来,命令行里的 -v 参数会变得极难维护。我建议从第二步开始就用 Compose 文件把挂载配置固化下来。
以一个小应用为例,它需要一个持久化数据目录、一个只读配置文件、一块临时缓存:
yaml复制services:
app:
image: example/app:2.1
ports:
- "8080:8080"
volumes:
- app_data:/var/lib/app
- ./config/app.conf:/etc/app/app.conf:ro
- type: tmpfs
target: /var/cache/app
tmpfs:
size: 268435456
volumes:
app_data:
注意几个细节:顶层 volumes 里声明了 app_data,Compose 会把它当作用部管理的命名卷,卷名前还会自动加上项目前缀,避免多个项目之间的卷重名。绑定挂载支持相对路径,但相对的是 Compose 文件所在目录,不是执行 docker compose up 时所在的目录,这点比较反直觉。tmpfs 的 size 单位是字节,上面这个配置是 256MB。
这套配置的好处是,任何一个同事拿到 Compose 文件,都能在一个全新的环境里复现出完全一样的挂载结构,不会出现“我明明挂载了,怎么容器里没有”的问题。
3.3 镜像自带的初始化脚本与空卷之间的先后关系
数据库镜像大多内置初始化逻辑。以官方 MySQL 镜像为例,容器首次启动时会检查挂载的数据目录是否为空,如果为空,则会执行 /docker-entrypoint-initdb.d 目录下的初始化脚本,创建库表;如果不为空,则默认已有初始化好的数据库,跳过脚本。
这个机制带来了一个典型陷阱:有人把宿主机上一个非空目录(比如里面放了个 readme.txt 或者根目录自己建的空文件夹)挂载给 MySQL 的 /var/lib/mysql,MySQL 看到数据目录不是空的,就认为数据库已经初始化过了,直接跳过初始化脚本。结果端口正常监听,但连接进去没有预期的库表,日志里也没有明显的报错。
根据我的经验,处理这个问题的正确姿势是:先让 MySQL 用自己的匿名卷或空白命名卷完成初始化,确认库表创建成功后再规划正式的持久化方案;如果非要直接挂宿主机目录,务必确保宿主机目录完全为空再挂。初始化脚本只会执行一次,后面你就算删掉脚本也不会重来一遍,所以最好在初始化完成后第一时间做一次卷备份,后面随时能回滚。
4. 挂载后权限混乱的排查过程:从 Permission denied 到最终根治
4.1 现象描述:容器启动日志里的 Permission denied
有一次我在某个项目里部署一个开源数据库,镜像直接用官方提供的,启动命令大致是这样:
bash复制docker run -d \
-v /srv/postgres-data:/var/lib/postgresql/data \
--name pgtest \
postgres:15
容器起来没几秒就退出了,日志里反复出现类似这样的报错:
code复制FATAL: data directory "/var/lib/postgresql/data" has invalid permissions
chmod: changing permissions of '/var/lib/postgresql/data': Operation not permitted
这种报错对不熟悉的人来说很“迷”,因为目录本身看起来是可写的。实际上问题出在身份不匹配:容器内的数据库进程以某个固定的系统用户运行,而这个用户在宿主机上并不存在,也没法直接映射到宿主机目录的属主身上。宿主机 /srv/pgtest 目录的属主可能是一台机器上的普通用户 UID 1000,而容器内进程需要的用户则是另一个固定 UID。两边用户对不上,自然没有权限写入。
4.2 排查链路:先确认进程身份,再看目录身份,最后设计对齐策略
遇到权限问题我的排查顺序是固定的:先看报错发生在哪个路径,再确认容器里跑进程的用户身份,最后看宿主机目标目录的属主和权限。
第一步是从日志里拿到确切的路径,以上面的报错为例,问题路径是 /var/lib/postgresql/data。
第二步确认容器内进程以什么用户运行。可以用 docker exec 进容器执行 id,但当时容器已经启动失败退出了,所以我会用另一个方式:
bash复制docker run --rm --user root \
-v /srv/postgres-data:/var/lib/postgresql/data \
--entrypoint id \
postgres:15
这个命令用镜像自带的 id 工具,以 root 身份进入容器,看到的是运行数据库前的基础用户信息。通常你还会需要确认数据库进程真正使用的用户,这要翻官方镜像文档或看 /docker-entrypoint.sh 脚本,不过大部分官方镜像都有约定:要么固定 UID,要么允许用环境变量指定。
第三步看宿主机目标目录的属主:
bash复制ls -lnd /srv/postgres-data
ls -lnd 不跟随符号链接,显示的是目录本身的属主和权限,避免被链接误导。把容器内进程需要的 UID 和宿主机目录当前属主 UID 一对比,问题就清晰了:两侧身份不一致。
4.3 三种根治方式与各自的取舍
解决权限问题有几个方向,各有取舍:
| 解决方式 | 操作 | 优点 | 缺点 |
|---|---|---|---|
| 临时放开权限 | chmod 777 /srv/... |
立刻能用,适合一次性验证 | 安全隐患大,多用户环境下极度不推荐 |
| 修改宿主机目录属主 | chown 999:999 /srv/... |
彻底解决,目录归属与容器内用户一致 | 需要知道镜像内进程的固定 UID;宿主机其他服务可能受影响 |
| 启动容器时指定用户 | Compose 里配 user: "1000:1000" 并保证宿主机目录属主为 UID 1000 |
可控性强,不折腾宿主机目录权限 | 需要镜像内的应用对运行用户不敏感,某些镜像要求必须 root 启动 |
有两点坑我要特别提醒。第一,很多人会想当然以为“在容器里 chmod 一下目录就能修复”,但容器重建后目录权限是按宿主机文件系统实际属主来认的,容器内的 chmod 对绑定挂载目录可能有效,但宿主机目录的属主如果不改,同样的问题下次还会出现。第二,不要轻易用 user: root 去掩盖问题,尤其是对外提供服务的应用,root 权限在容器里被攻击后很容易影响宿主机。
4.4 最容易触发这个坑的典型场景
权限问题最容易出现在“宿主机目录先建好再交给容器”的场景。宿主机上你用自己的账号新建了一个目录,属主是你的 UID,接下来启动容器往里写数据,容器内进程又是一个固定 UID,两边冲突。
另一个高频场景是 rootless 模式下的容器运行。由于宿主机用户本身没有管理员权限,Docker 对挂载目录的所有权检查更严格,目录属主不匹配时经常直接拒绝写入。我的建议是:凡是要做正式部署,先把宿主机目录的属主定好,再启动容器,不要用“容器启动后发现问题再补 chmod”的思路,那种思路在开发环境能糊弄过去,在生产环境会留下长期隐患。
5. 读写性能与 IO 优化:delegated、tmpfs 与写放大
5.1 Linux 上被高估的一段参数:delegated / cached / consistent
聊到优化挂载性能,很多文章会提 delegated、cached、consistent 这三个词。官方文档确实讲到这些选项可以影响绑定挂载的性能与一致性,但有个重要前提往往被忽略——这些选项只在 Docker Desktop(macOS 和 Windows)上对绑定挂载有意义,因为它们解决的是“虚拟机文件系统与宿主机文件系统之间的同步开销”。
在 Linux 原生的 Docker Engine 上,绑定挂载就是直接的目录映射,没有那层跨系统同步,所以你在 --mount 里写 consistency=delegated 不会报错,但实际上也不会生效。不少 Linux 服务器管理员在优化参数列表里抄了这段配置,属于纯折腾。
Linux 上真正影响挂载性能的因素要朴素得多:底层硬件是机械盘还是 SSD,目录所在文件系统的类型与挂载参数,以及运行时是否开启了额外的 IO 限制。这几个维度比写几个花哨参数实在得多。
5.2 tmpfs 挂载:把内存当临时目录用的正确姿势
临时缓存类数据放在持久化卷里其实是一种浪费,频繁写入还会增加磁盘磨损。这种情况适合用 tmpfs,直接把数据放到内存里。
启动容器时可以这样用:
bash复制docker run -d \
--mount type=tmpfs,target=/var/cache/app,tmpfs-size=536870912 \
example/app:2.1
tmpfs 挂载的特点是读写速度极快,因为走的是内存;但数据不持久,容器停止时挂载被移除,数据全部消失。它适合的场景包括:应用运行时生成的临时文件、编译过程中的中间产物、可重建的缓存数据。
有一个常见的认知误区是“tmpfs 会占用容器内存配额”。实际上 tmpfs 占用的是整个容器可用内存的一部分,如果容器本身设置了内存限制,tmpfs 写入过大可能导致 OOM。所以设定 tmpfs 大小要留出余量,不要顶到容器内存上限。
5.3 日志落盘与写放大:限制 json-file 日志文件大小
高并发容器一个容易被忽略的 IO 压力来源是日志。默认情况下 Docker 会把容器 stdout 输出写入 json-file 日志文件,这个文件的增长可能非常快。如果容器里跑了大量打印日志的应用,哪怕业务数据量不大,宿主机磁盘也会被日志刷得厉害,尤其是多个容器同时刷日志的场景,磁盘 IO 会被无意义地占满。
我的常规配置是在 Compose 或 Docker 守护进程配置里给日志加上滚动限制:
yaml复制services:
app:
image: example/app:2.1
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
max-size 限制单个日志文件最大 10MB,max-file 保留最多 3 个日志文件。达到上限后旧日志会被轮转清理掉。这样磁盘使用被严格约束,不会出现日志把整个磁盘撑爆的半夜事故。如果你们有独立的日志采集系统,还可以考虑直接把驱动改为 none,让应用日志直接丢弃或由外部采集器接管,但那样本地排障时会少一个日志来源,取舍要看团队习惯。
5.4 并发写同一卷:一个说大不大说小不小的坑
最后一个性能相关提醒是并发问题。命名卷可以同时挂载给多个容器,这在架构上很灵活,但也带来了数据一致性的风险。卷本身只是存储,不提供跨进程的文件锁,多个容器同时写同一个文件时,轻则互相覆盖,重则文件内容损坏。
根据经验,比较稳妥的做法是:同一个卷同时只允许一个容器执行写入,其他容器只读。需要多实例写入的场景,要么按实例拆分卷,要么引入分布式存储或数据库,而不是指望 Docker 卷帮你解决并发写问题。
6. 数据卷的备份、迁移与日常维护
6.1 停止容器后打包:最稳妥的备份方式
数据卷的备份不需要安装任何额外工具,用 Docker 自带的机制就能完成。最稳妥的流程是:先停容器,避免备份过程中数据还在被写入导致内容不一致,然后起一个临时容器,把目标卷和备份目录分别挂进去,用 tar 打包。
bash复制docker run --rm \
-v appdata:/data \
-v $(pwd):/backup \
-w /backup \
alpine tar czf appdata-2025-06-01.tar.gz -C /data .
这个命令做了几件事:以 --rm 模式运行一个临时 alpine 容器(用完后自动删除,不留垃圾);把命名卷 appdata 挂到容器内 /data;把当前目录挂到 /backup;归档 /data 目录的内容到备份文件。
为什么选 tar 而不是 docker cp?如果数据量不大且文件不复杂,docker cp 也能用,但数据卷里可能包含特殊的属主、权限位、软链接,docker cp 的兼容性不如 tar 稳。tar 是通用归档工具,备份出来的文件在任意 Linux 机器上都能解包还原,后续做迁移也更方便。
6.2 迁移到另一台机器:打包再解包
把数据从一台机器迁到另一台,核心思路就是“打包、传输、解包”。在源机器上执行 6.1 的备份命令,把 tar 文件传到目标机器,然后创建一个新的命名卷,临时挂载解包:
bash复制docker volume create newdata
docker run --rm \
-v newdata:/data \
-v $(pwd):/backup \
-w /backup \
alpine sh -c "tar xzf appdata-2025-06-01.tar.gz -C /data"
解包完成后,用 docker run -v newdata:/data ... 启动正式容器即可。这里有两个小细节:第一,解包前确保卷是空的或者旧内容已备份,否则 tar 不会主动覆盖同名文件,可能造成新旧数据混在一起;第二,解包会保留 tar 包内的文件属主和权限,所以解包后大概率也会遇到第 4 章说的权限对齐问题,建议解包后顺手检查一下目录属主。
6.3 镜像升级时的数据保留策略
升级镜像版本时,最容易出问题的是“新镜像的数据目录路径变了”或者“启动命令漏写了挂载参数”。我的标准操作是:升级前先备份;升级时保持卷名不变;升级后用旧数据目录验证新版本服务正常;验证通过后再清理旧的备份。
具体来说,假设当前用 v1.0 版本,数据在 appdata 卷里。升级前先手动起一个临时容器确认卷当前内容可读:
bash复制docker run --rm -v appdata:/data alpine ls -l /data
升级时不要删卷,只删容器,用同一条挂载参数拉起 v2.0 镜像。新镜像如果也使用同一个数据目录路径,卷里的数据自然被读取。这里有个反直觉的坑:如果新镜像把默认数据路径改了,比如从 /var/lib/app 改到 /opt/app,你还是挂同一个卷,但数据目录看不到,应用会初始化一个空库。升级前最好去镜像仓库看下文档,确认数据目录路径没变。
6.4 悬空卷清理与备份纪律
匿名卷有个特点:容器删除后如果没显式加 -v 参数,匿名卷会变成悬空卷,继续占用磁盘空间。时间久了宿主机上会堆出很多没有归属的卷。
查看悬空卷:
bash复制docker volume ls -f dangling=true
清理悬空卷:
bash复制docker volume prune
但这里一定要有备份纪律:docker volume prune 会删除所有未被容器引用的卷,包括你几个月前遗忘的数据库卷。执行前务必用 docker volume ls 确认哪些卷还在使用,或者先把卷列表导出存档。删除卷的操作不可逆,比删容器严重得多。
我的习惯是:每个项目用统一的前缀命名卷,例如 projectname-postgres-data、projectname-upload-files;备份脚本每周自动跑一次,只保留最近 7 天的 tar 包;涉及数据库升级、迁移、清理前,第一件事永远是做卷备份,而不是直接执行环境清理命令。
最后再说一点实际体会。我在团队里定了几条规矩:写进生产配置的卷一律用命名卷并带项目前缀;宿主机动态文件一律绑定挂载且能只读就只读;临时缓存一律 tmpfs;备份只认固定日期的 tar 包,不做无跟踪的增量。这几条规则未必适合所有项目,但确实让我没再因为“数据卷没挂好”半夜爬起来处理事故。挂载这件事,刚开始在 -v、--mount、卷名和路径之间绕得头晕,一旦你把“数据生命周期”和“容器生命周期”分开想明白,很多问题自己就顺了。
