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