Docker 数据卷

容器有个铁律:容器删除,数据丢失。这对临时服务无所谓,但数据库、用户上传文件、日志这种需要持久化的数据就是灾难。Docker 提供 Volume(数据卷)机制,把数据放到容器之外,让数据拥有"独立于容器生命周期"的存在。本章彻底讲清。

为什么需要 Volume

先看一个反例,直观感受"数据丢失":

# 容器有个铁律:容器删除,数据丢失

# 跑一个 postgres 容器,建几张表
docker run -d --name db -e POSTGRES_PASSWORD=secret postgres:16
docker exec -it db psql -U postgres -c "CREATE TABLE users (id int);"

# 删除容器(数据没了!)
docker stop db && docker rm db

# 重新跑一个,表全没了
docker run -d --name db -e POSTGRES_PASSWORD=secret postgres:16
docker exec -it db psql -U postgres -c "\dt"
# 找不到任何表,因为数据跟着旧容器一起被删了

# 解决:用 Volume 把数据放到容器外
docker run -d --name db -e POSTGRES_PASSWORD=secret -v dbdata:/var/lib/postgresql/data postgres:16
# 现在删容器、重建容器,数据都还在

容器本质是"镜像 + 一层可写层"。可写层跟着容器走,容器删了它就没了。Volume 的作用就是把需要长期保留的数据从可写层剥离,放到主机上由 Docker 管理的目录里。容器删除、重建,只要挂同一个卷,数据就还在。

三种挂载方式

Docker 提供三种把主机存储接进容器的机制,各有用途:

# 三种挂载方式

# 1. 命名卷(named volume):Docker 管理,跨容器共享,生产首选
docker volume create mydata
docker run -v mydata:/app/data myapp

# 2. 匿名卷(anonymous volume):没名字,Docker 自动起一长串哈希
docker run -v /app/data myapp          # 主机路径自动分配
# 删除容器时如果不带 -v,匿名卷会变成"悬空卷",占空间

# 3. 绑定挂载(bind mount):映射主机目录,开发常用
docker run -v /home/me/project:/app myapp           # 老语法
docker run -v $(pwd)/src:/app/src myapp              # 当前目录
docker run --mount type=bind,source=$(pwd)/src,target=/app/src myapp   # 新语法,更明确

# 4. tmpfs:只存内存,容器停止即消失(适合临时密钥、缓存)
docker run --tmpfs /app/cache myapp
docker run --mount type=tmpfs,target=/app/cache myapp

选型口诀:生产用命名卷,开发用绑定挂载,敏感数据用 tmpfs

docker volume 全套命令

# docker volume 全套管理命令

# 创建卷
docker volume create mydata              # 默认 local 驱动
docker volume create --driver local --opt type=nfs mydata   # NFS 等高级驱动

# 列出所有卷
docker volume ls
# DRIVER    VOLUME NAME
# local     mydata
# local     dbdata

# 看卷的详细信息(在主机上的真实路径、挂载点、驱动)
docker volume inspect mydata
# Mountpoint: /var/lib/docker/volumes/mydata/_data

# 删除卷(没有容器在用才能删)
docker volume rm mydata

# 删除所有未被使用的卷(释放磁盘)
docker volume prune
docker volume prune -a                   # 删除所有未挂载的卷

# 删除容器时连带删除其匿名卷
docker rm -v container_name

几个常用场景:docker volume prune 清理无用卷(磁盘满了时常用);docker volume inspect 能看到卷在主机上的真实路径(Linux 是 /var/lib/docker/volumes/xxx/_data);docker rm -v 删容器时同时删匿名卷,避免悬空卷累积。

绑定挂载:本地开发的神器

绑定挂载最大的价值是实时同步——主机改代码,容器里立刻看到,实现热重载:

# 绑定挂载:本地开发的杀手锏

# 把当前目录的源码映射进容器,改代码立即生效
docker run -d --name dev \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/package.json:/app/package.json:ro \
  -p 3000:3000 \
  node:20-alpine

# :ro 只读挂载(容器内不能改,但主机能改,适合配置文件)
# :rw 读写(默认)

# 用 --mount 新语法更清晰(推荐)
docker run -d --name dev \
  --mount type=bind,source=$(pwd)/src,target=/app/src \
  --mount type=bind,source=$(pwd)/.env,target=/app/.env,readonly \
  node:20-alpine

# 经典用法:本地代码 + 容器内运行时,实现"热重载"
# 改源码 → nodemon / webpack-dev-server 监听到变化 → 自动重启
# 整个开发流程不用在主机装 Node,只装 Docker 就够了

这种"主机代码 + 容器运行时"的开发模式好处巨大:不用在主机装 Node/Python/Ruby 一堆版本管理工具,每个项目用自己镜像里的版本,互不污染。新人入职 clone 项目 + docker compose up,几分钟就有完整开发环境。:ro(readonly)适合配置文件,防止容器内意外修改主机文件。

备份与迁移

命名卷虽然由 Docker 管理,但本质还是主机上的文件,可以打包搬运:

# 数据卷的备份与迁移

# 思路:用临时容器挂载卷,把数据 tar 出来

# 备份:把 mydata 卷的内容打包成 tar
docker run --rm -v mydata:/data -v $(pwd):/backup alpine \
  tar czf /backup/mydata-backup.tar.gz -C /data .

# 恢复:把 tar 解到新卷
docker volume create mydata-restored
docker run --rm -v mydata-restored:/data -v $(pwd):/backup alpine \
  tar xzf /backup/mydata-backup.tar.gz -C /data

# 迁移到另一台机器
# 1. 备份成 tar
# 2. scp / rsync 拷到新机器
# 3. 在新机器上恢复

# 也可以用 save/load 搬运整个镜像,但卷里的数据不包含在镜像里
# 镜像 = 应用代码,卷 = 运行时数据,两者要分开管理

记住一个关键认知:镜像(docker save/load)包含应用代码,不包含卷里的运行时数据。数据库的表、上传的文件都在卷里,迁移时要单独备份卷。生产环境通常用云厂商的托管数据库或 NFS 卷,避免单机故障丢数据。

实战:给 Postgres 配持久化

把上面的知识串起来,做一个"删了容器数据还在"的真实例子:

# 实战:给一个 Postgres 配持久化

# 1. 创建命名卷
docker volume create pgdata

# 2. 跑 postgres,把数据目录挂到卷
docker run -d --name db \
  -e POSTGRES_USER=app \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=app \
  -v pgdata:/var/lib/postgresql/data \
  -p 5432:5432 \
  postgres:16-alpine

# 3. 建表、插数据
docker exec -it db psql -U app -c "CREATE TABLE users (id serial, name text);"
docker exec -it db psql -U app -c "INSERT INTO users (name) VALUES ('alice');"

# 4. 模拟"灾难":删除容器
docker stop db && docker rm db

# 5. 重新跑(用同一个卷),数据还在!
docker run -d --name db \
  -e POSTGRES_USER=app \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=app \
  -v pgdata:/var/lib/postgresql/data \
  -p 5432:5432 \
  postgres:16-alpine

docker exec -it db psql -U app -c "SELECT * FROM users;"
# 仍然能看到 alice 这条记录

# 6. 看卷的真实位置
docker volume inspect pgdata

走完这一遍,你就理解了 Volume 的全部价值。生产里 99% 的有状态服务(数据库、消息队列、文件存储)都要这么配。无状态服务(Web 应用、API)则故意不挂卷,这样能随时水平扩容、随时重建。

常见坑

← 上一篇 Dockerfile 详解

下一篇 Docker 网络

✈️💬