Skip to content
Docker 数据卷管理

Docker 数据卷管理

Docker 容器的文件系统是临时的——容器删除后,内部数据会丢失。Docker 提供了三种持久化数据的方式。


三种挂载方式对比

特性 Bind Mount Named Volume tmpfs
存储位置 宿主机任意路径 Docker 管理的目录 内存
容器删除后数据 保留 保留 丢失
跨容器共享 ✅ ✅ ❌
备份难度 直接 需通过容器 不支持
可移植性 依赖宿主机路径 独立于宿主机 临时
性能 磁盘 磁盘(可选驱动) 极快(内存)

Bind Mount(绑定挂载)

将宿主机上指定的目录或文件直接挂载到容器中。双向可见。

# 格式:-v 宿主机绝对路径:容器路径[:选项]
docker run -v /home/user/data:/app/data nginx

# 只读挂载 ro: read only
docker run -v /home/user/config:/app/config:ro nginx

# 单个文件挂载
docker run -v /home/user/nginx.conf:/etc/nginx/nginx.conf:ro nginx

# 使用 --mount 长语法(推荐)
docker run --mount type=bind,source=/home/user/data,target=/app/data nginx
docker run --mount type=bind,source=/home/user/data,target=/app/data,readonly nginx

选项说明

选项 说明
ro 只读挂载
rw 读写挂载(默认)
z 共享 SELinux 标签(多容器共享)
Z 私有 SELinux 标签
consistent 主机和容器数据一致(默认)
cached 主机视角为准,允许延迟
delegated 容器视角为准,允许延迟

💡 最佳实践:优先使用 --mount 而非 -v。--mount 语法更明确,不会在源目录不存在时静默创建空目录。

🚨 陷阱:docker run -v /host/path:/container/path 中,如果 host 路径不存在,Docker 会自动创建一个空目录。这在绑定挂载中可能造成困惑——如果你以为挂载了一个有数据的目录,实际上挂的是一个空目录。使用 --mount 语法则会在源路径不存在时报错。


Named Volume(命名卷)

由 Docker 管理,存储在 /var/lib/docker/volumes/ 下。独立于宿主机目录结构。

# 创建卷
docker volume create my_data
docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/export/my_data \
  nfs_volume

# 查看卷
docker volume ls
docker volume inspect my_data

# 使用卷
docker run -v my_data:/app/data nginx
docker run --mount type=volume,source=my_data,target=/app/data nginx

# 删除卷
docker volume rm my_data
docker volume prune   # 删除所有未使用的卷

命名卷 vs 绑定挂载

# 命名卷:第一次使用时会从容器中初始化数据到卷中
docker run -v my_volume:/var/lib/mysql mysql:8.0
# → 如果 /var/lib/mysql 有初始数据,会被复制到 my_volume 中

# 绑定挂载:总是用宿主机内容覆盖容器内容
docker run -v /data/mysql:/var/lib/mysql mysql:8.0
# → /data/mysql 的内容覆盖容器内 /var/lib/mysql(空则变成空)

🔬 深入原理:命名卷在首次绑定时有一个特殊的初始化行为:如果卷是空的,Docker 会把容器内该挂载点的内容复制到卷中。这意味着你可以在 Dockerfile 中往目录放默认数据,使用时首次挂载卷会自动填充。绑定挂载不会做这件事——宿主机目录的内容直接覆盖容器目录。


tmpfs(内存临时卷)

数据存储在内存中,不持久化到磁盘,容器停止即丢失。

# 使用 --tmpfs(短语法)
docker run --tmpfs /app/cache nginx

# 使用 --mount(长语法)
docker run --mount type=tmpfs,target=/app/cache,tmpfs-size=64M nginx

# Compose
# services:
#   app:
#     tmpfs:
#       - /app/cache:size=64M,noexec
适用场景 说明
缓存目录 不需要持久化的临时缓存
安全敏感数据 退出自动清除,不留痕迹
高 IOPS 场景 内存读写远快于磁盘

卷的备份与恢复

备份

# 通过临时容器备份命名卷
docker run --rm \
  -v my_data:/source:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/my_data_backup.tar.gz -C /source .

# 备份数据库卷
docker run --rm \
  -v db_data:/var/lib/mysql:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/db_backup_$(date +%Y%m%d).tar.gz -C /var/lib/mysql .

恢复

# 恢复到命名卷
docker run --rm \
  -v my_data:/target \
  -v $(pwd):/backup \
  alpine tar xzf /backup/my_data_backup.tar.gz -C /target .

迁移

# 将数据从一个卷迁移到另一个
docker run --rm \
  -v old_volume:/source:ro \
  -v new_volume:/target \
  alpine sh -c "cp -a /source/. /target/"

卷驱动(Volume Drivers)

# 本地 NFS 卷
docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.10,nolock,soft,rw \
  --opt device=:/path/on/nfs \
  nfs_data

# SSHFS 卷(需要 vieux/sshfs 插件)
docker plugin install vieux/sshfs
docker volume create --driver vieux/sshfs \
  --opt sshcmd=user@remote:/data \
  --opt password=secret \
  remote_data
驱动 场景
local 默认本地存储
nfs 挂载网络文件系统
btrfs 支持快照的高级卷
第三方插件 AWS EBS、Azure Disk、GCE Persistent Disk 等

权限问题

# 宿主机与容器 UID 不匹配
# 宿主机文件 owner: 1000,容器内进程以 1001 运行 → 权限拒绝

# 解决方案 1:容器以宿主机文件 owner 的 UID 运行
docker run --user 1000:1000 -v /host/data:/app/data nginx

# 解决方案 2:Dockerfile 中调整权限
# RUN chown -R 1000:1000 /app/data

# 解决方案 3:使用命名卷(Docker 管理,无权限问题)

🚨 陷阱:绑定挂载的文件权限由宿主机决定,容器内的 chown 不会生效(除非以 root 运行)。命名卷则在 Docker 管控下,权限更可控。


Dockerfile 中的 VOLUME

# 声明挂载点——但每次 docker run 会创建匿名卷
VOLUME /var/lib/mysql

# ❌ 不推荐在 Dockerfile 中使用 VOLUME
# 原因:
# 1. 匿名卷难以管理和清理
# 2. 后续 RUN 指令无法修改 VOLUME 目录的内容
# 3. 会降低 Dockerfile 的可预测性

# ✅ 推荐:在 docker run 或 Compose 中显式指定卷

磁盘空间管理

# 查看卷占用
docker system df
docker system df -v

# 查看具体卷大小(需要进入 Docker 数据目录)
du -sh /var/lib/docker/volumes/my_data/_data

# 清理未使用的卷
docker volume prune         # 删除所有未使用的卷
docker volume prune -a      # 删除所有未被至少一个容器使用的卷
docker volume prune --filter "label!=keep"

# 清理悬空镜像和停止的容器
docker system prune -a --volumes

常见陷阱

陷阱 说明 正确做法
绑定挂载路径不存在 -v 会自动创建空目录 使用 --mount 语法(报错而非静默创建)
权限不匹配 宿主机/容器 UID 不一致 用 --user 指定 UID 或使用命名卷
匿名卷堆积 VOLUME 指令产生匿名卷 避免在 Dockerfile 中写 VOLUME
删除容器忘记删卷 旧卷占用磁盘空间 用 docker system prune 定期清理
卷的初始化被覆盖 绑定挂载会覆盖容器内数据 需要初始化时考虑命名卷
卷挂载到子目录 如果挂载点下已有文件,行为不一致 将卷挂载到空目录