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 定期清理
卷的初始化被覆盖 绑定挂载会覆盖容器内数据 需要初始化时考虑命名卷
卷挂载到子目录 如果挂载点下已有文件,行为不一致 将卷挂载到空目录