跳到主要内容
预计阅读 18 分钟

数据放哪儿才不会丢 —— 数据卷与持久化存储方案详解

容器是短命的,数据是长命的。学会正确地管理数据存储,是让容器化应用可靠运行的关键一步。

📋 开篇自测:你已经知道多少?

  1. 容器被删除后,里面的数据还在吗?如何避免数据丢失?
  2. Docker Volume和Bind Mount有什么区别?
  3. 你能说出至少两个需要持久化存储的典型场景吗?

一、容器的”健忘症”

在前面的章节里我们反复强调过一个事实:容器是有生命周期的。它被创建、运行、停止,最终被删除。而当容器被删除时,它内部的所有数据都会随之消失

这就像在黑板上写字——课上写的内容再精彩,擦了就没了。如果你要保留笔记,就得抄到本子上。

对于无状态应用(比如一个纯计算的API服务),这不是问题。但对于需要持久化数据的场景,这就是个致命的缺陷:

  • 数据库容器:用户数据、订单记录总不能容器一重启就全没了吧?
  • 日志收集:应用日志需要保存下来供后续分析
  • 用户上传的文件:用户上传的头像、文档不能说丢就丢
  • 配置文件:需要在不重建容器的情况下更新配置

Docker提供了三种主要的数据持久化方案来解决这个问题:Volume(数据卷)Bind Mount(绑定挂载)tmpfs Mount(临时文件系统挂载)


二、Volume(数据卷)—— Docker推荐的持久化方式

Volume是Docker自己管理的存储区域。你可以把它想象成Docker替你在宿主机上开辟了一块”仓库空间”,专门用来存放需要持久化的数据。

为什么Volume是首选

打个比方:你住在出租屋(容器)里,房间里的东西随时可能因为搬家(删除容器)而清空。但你在小区里租了一个储物间(Volume),不管你搬多少次家,储物间里的东西一直在。

Volume的优势在于:

  • 由Docker管理:你不需要操心数据存在宿主机的哪个角落
  • 跨平台兼容:在Linux、macOS、Windows上行为一致
  • 方便备份和迁移:Docker提供了专门的命令来操作
  • 支持驱动扩展:可以使用远程存储、云存储等驱动

基本操作

# 创建一个命名Volume
docker volume create my-data

# 查看所有Volume
docker volume ls

# 查看Volume详情(可以看到它在宿主机上的实际路径)
docker volume inspect my-data
# 输出中的 "Mountpoint" 就是实际存储路径
# 在Linux上通常是 /var/lib/docker/volumes/my-data/_data

# 删除Volume
docker volume rm my-data

# 清理所有未被任何容器使用的Volume
docker volume prune

在容器中使用Volume

# 方式一:使用命名Volume
docker run -d \
  --name my-postgres \
  -v pgdata:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret123 \
  postgres:16

# 方式二:使用 --mount 语法(更明确、更推荐)
docker run -d \
  --name my-postgres \
  --mount source=pgdata,target=/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret123 \
  postgres:16

-v pgdata:/var/lib/postgresql/data 的意思是:把名为 pgdata 的Volume挂载到容器内的 /var/lib/postgresql/data 目录。如果 pgdata 这个Volume不存在,Docker会自动创建它。

现在让我们验证数据的持久性:

# 进入PostgreSQL容器,创建一些数据
docker exec -it my-postgres psql -U postgres -c "CREATE TABLE test (id int, name varchar(50));"
docker exec -it my-postgres psql -U postgres -c "INSERT INTO test VALUES (1, 'Docker');"
docker exec -it my-postgres psql -U postgres -c "SELECT * FROM test;"

# 删除容器
docker rm -f my-postgres

# 用同一个Volume重新创建容器
docker run -d \
  --name my-postgres-new \
  -v pgdata:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret123 \
  postgres:16

# 数据还在!
docker exec -it my-postgres-new psql -U postgres -c "SELECT * FROM test;"
# 输出:
#  id |  name
# ----+--------
#   1 | Docker

容器删了,数据不丢。这就是Volume的威力。

🤔 想一想 Volume的数据存在宿主机的特定目录中。如果宿主机的硬盘坏了,Volume的数据也会丢失。在生产环境中,你觉得应该如何做额外的数据保护?


三、Bind Mount(绑定挂载)—— 开发者的好帮手

Bind Mount是把宿主机上的一个特定目录或文件直接挂载到容器内。与Volume由Docker管理不同,Bind Mount完全取决于宿主机的文件系统结构。

Volume vs Bind Mount

用搬家的比喻继续说:

  • Volume:小区提供的储物间,你不需要关心它具体在哪栋楼、几层几号。
  • Bind Mount:你在自己家里腾出一个抽屉,把钥匙给了容器,让它也能用这个抽屉。你清楚地知道抽屉在哪里,容器里看到的内容和你家抽屉里的完全一样。

基本用法

# 把当前目录挂载到容器的 /app 目录
docker run -d \
  --name dev-server \
  -v $(pwd):/app \
  -p 3000:3000 \
  node:20-alpine \
  sh -c "cd /app && npm install && npm run dev"

# 使用 --mount 语法
docker run -d \
  --name dev-server \
  --mount type=bind,source=$(pwd),target=/app \
  -p 3000:3000 \
  node:20-alpine \
  sh -c "cd /app && npm install && npm run dev"

开发场景中的杀手级应用

Bind Mount在本地开发中特别有用。想象这个场景:你在开发一个Node.js应用,希望改完代码后立即看到效果,而不需要每次都重新构建镜像。

# 把本地代码目录挂载进容器
docker run -d \
  --name my-dev \
  -v $(pwd)/src:/app/src \
  -p 3000:3000 \
  my-node-app

现在你在本地编辑器里修改 src/ 目录下的任何文件,容器里立刻就能看到变化。如果配合热重载(Hot Reload)工具,改完代码、保存,浏览器自动刷新——开发体验丝滑无比。

只读挂载

有时候你只想让容器读取宿主机的文件,不允许修改。比如挂载配置文件:

# 加上 :ro 后缀表示只读
docker run -d \
  --name my-nginx \
  -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
  -v $(pwd)/html:/usr/share/nginx/html:ro \
  -p 80:80 \
  nginx

容器可以读取这些文件,但任何写入操作都会被拒绝。这提供了一层额外的安全保障。

⚠️ 常见误区

  • 误区一:“Bind Mount和Volume是一回事”。它们的使用语法很像,但管理方式完全不同。Volume由Docker管理,数据存在Docker的存储区域;Bind Mount由用户指定宿主机路径,Docker只负责挂载。
  • 误区二:“Bind Mount适合生产环境”。不推荐。Bind Mount依赖宿主机的目录结构,不同机器上路径可能不一样,可移植性差。生产环境应该使用Volume。
  • 误区三:“容器里修改了挂载的文件,宿主机上也会改变”——这不是误区,这是事实!Bind Mount是双向同步的,在容器里改了文件,宿主机上确实会改变,反之亦然。这既是它的优点也是它的风险点。

四、tmpfs Mount —— 内存中的临时存储

tmpfs Mount把数据存储在宿主机的内存中,而不是磁盘上。容器停止后数据就消失了。

docker run -d \
  --name secure-app \
  --tmpfs /tmp:size=100m \
  my-app

适用场景:

  • 存储敏感信息(密钥、令牌等),不希望写入磁盘留下痕迹
  • 临时计算结果,追求最快的I/O速度
  • 需要快速读写但不需要持久化的缓存数据

五、三种挂载方式的对比

特性VolumeBind Mounttmpfs Mount
存储位置Docker管理的区域宿主机任意目录宿主机内存
持久化
可移植性差(依赖宿主机路径)N/A
性能最好
适用场景生产环境数据持久化本地开发热重载敏感数据/临时缓存
Docker管理
多容器共享支持支持不支持

选择建议:

  • 生产环境数据库、文件存储 → Volume
  • 本地开发代码同步 → Bind Mount
  • 临时敏感数据 → tmpfs Mount
  • 拿不准的时候 → Volume(Docker推荐的默认选择)

六、实战:数据卷的高级用法

多个容器共享一个Volume

# 创建一个共享Volume
docker volume create shared-logs

# 应用容器写日志
docker run -d \
  --name app \
  -v shared-logs:/var/log/app \
  my-app

# 日志收集容器读日志
docker run -d \
  --name log-collector \
  -v shared-logs:/logs:ro \
  my-log-collector

应用容器往 /var/log/app 写日志,日志收集容器从 /logs 读取同样的内容——它们看到的是同一份数据。

Volume数据备份

# 备份Volume到tar文件
docker run --rm \
  -v pgdata:/source:ro \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/pgdata-backup.tar.gz -C /source .

# 从tar文件恢复到Volume
docker run --rm \
  -v pgdata-restored:/target \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/pgdata-backup.tar.gz -C /target

这个技巧值得解释一下:我们启动了一个临时的Alpine容器(--rm 表示用完即删),同时挂载了源Volume和备份目标目录,然后用tar命令完成备份。整个过程不需要停止任何正在运行的服务。

在Dockerfile中声明Volume

FROM postgres:16
VOLUME ["/var/lib/postgresql/data"]

VOLUME 指令告诉Docker:这个目录需要持久化。当有人用这个镜像启动容器时,即使没有显式指定 -v,Docker也会自动创建一个匿名Volume来挂载这个目录。

不过要注意:匿名Volume没有友好的名称,管理起来不太方便。在实际使用中,最好还是在 docker run 时显式指定命名Volume。

使用Volume Driver扩展存储

Docker的Volume系统支持插件驱动,可以把数据存储到远程位置:

# 使用NFS驱动
docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/share \
  nfs-volume

# 各云厂商也有自己的Volume驱动
# AWS EBS、Azure Disk、阿里云盘等

这在集群环境中特别重要——当容器可能被调度到不同的物理机上运行时,数据需要存储在所有机器都能访问的共享存储上。

🤔 想一想 在数据库容器的使用中,是否应该把所有数据都放在Volume中?日志文件和数据文件是否应该放在不同的Volume中?这样做有什么好处?


七、数据管理的最佳实践

  1. 生产环境永远使用命名Volume,不要依赖匿名Volume或Bind Mount

  2. 定期备份Volume数据,Volume不是备份方案,它只是持久化方案

  3. 数据库容器一定要挂载Volume,这是最基本的要求

  4. 开发环境用Bind Mount,生产环境用Volume,各取所长

  5. 敏感配置用Docker Secrets或环境变量,不要硬编码在镜像中

  6. 及时清理不用的Volume,它们会占用大量磁盘空间

    # 查看Volume占用的空间
    docker system df -v
    
    # 清理未使用的Volume(谨慎操作!)
    docker volume prune
  7. 只读挂载配置文件,避免容器意外修改宿主机上的配置

  8. 分离数据和应用,不要把数据存在容器的可写层中


📝 掌握度自测

  1. 解释容器的”可写层”在容器删除后会发生什么,以及如何用Volume避免数据丢失。

  2. 你有一个MySQL容器,需要确保数据在容器重建后不丢失。请写出完整的docker run命令。

  3. Volume和Bind Mount在本地开发和生产环境中各自的适用场景是什么?

  4. 如何备份一个Docker Volume的数据?请描述具体步骤或写出命令。

  5. 在什么场景下你会使用tmpfs Mount?它和Volume的最大区别是什么?

💡 自我评估

  • 全部答对:数据持久化是容器化应用的关键环节,你已经掌握了。接下来可以学习如何用Docker Compose编排多容器应用了。
  • 答对3-4题:核心概念掌握得不错。建议动手做一次Volume的创建、使用、备份和恢复全流程。
  • 答对1-2题:重点理解”容器是临时的,数据需要额外存储”这个核心思想。先把Volume的基本用法练熟,其他的可以后续再深入。

购买课程解锁全部内容

告别「在我电脑上能跑」:Docker 容器化实战

¥29.90