Docker 持久化
Docker 持久化
简介
在 Docker 的使用过程中,容器本身的存储层是临时的——当容器被删除或重建时,存储在容器内部的文件也会随之消失。然而在实际的生产环境中,数据库数据、应用配置文件、日志文件等都需要持久保存,不能因为容器的生命周期而丢失。
Docker 持久化机制正是为了解决这个问题而设计的。它通过将宿主机上的文件或目录挂载到容器内部,使得容器可以读写宿主机上的数据,即使容器被删除,数据仍然安全地保存在宿主机上。理解 Docker 的三种数据挂载方式及其适用场景,是容器化部署的基础技能。
本文将全面介绍 Docker 的三种数据存储方式——Volume(数据卷)、Bind Mounts(绑定挂载)和 tmpfs mounts(临时挂载),并通过实际示例演示每种方式的使用方法、最佳实践和常见陷阱。
Docker 数据存储架构
容器存储层的工作原理
Docker 容器由多个只读层(Image Layers)和一个可读写层(Container Layer)组成:
┌─────────────────────────┐
│ Container Layer (RW) │ ← 容器的可读写层(临时数据)
├─────────────────────────┤
│ Image Layer 3 (RO) │ ← 镜像层(只读)
├─────────────────────────┤
│ Image Layer 2 (RO) │ ← 镜像层(只读)
├─────────────────────────┤
│ Image Layer 1 (RO) │ ← 镜像层(只读)
└─────────────────────────┘当容器删除时,可读写层(Container Layer)中的数据也会被删除。如果需要持久化数据,就必须使用数据卷或绑定挂载将数据存储在宿主机上。
Docker 支持的三种数据挂载方式
Docker 提供了三种不同的方式将数据从宿主机挂载到容器中:
| 类型 | 存储位置 | 管理方式 | 生命周期 | 适用场景 |
|---|---|---|---|---|
| Volume | /var/lib/docker/volumes/ | Docker 管理 | 独立于容器 | 数据库数据、共享数据 |
| Bind Mount | 宿主机任意位置 | 用户管理 | 依赖宿主机 | 配置文件、源代码 |
| tmpfs Mount | 宿主机内存 | 内核管理 | 容器生命周期 | 敏感数据、临时缓存 |

Volume(普通数据卷)
什么是 Volume
Volume 是 Docker 管理的存储区域,存放在宿主机的 /var/lib/docker/volumes/ 目录下。Docker 负责创建、管理和清理 Volume,用户不需要关心底层存储的具体位置。Volume 是 Docker 推荐的数据持久化方式。
基本操作
# 创建命名数据卷
docker volume create for_nginx
# 查看所有数据卷
docker volume ls
# 查看数据卷详细信息
docker volume inspect for_nginx
# 查看数据卷的宿主机路径
docker volume inspect for_nginx --format '{{.Mountpoint}}'
# 输出类似:/var/lib/docker/volumes/for_nginx/_data匿名数据卷与命名数据卷
# 创建容器时未指定数据卷名称,Docker 自动创建匿名数据卷
docker run -d -p 80:80 -v /usr/share/nginx/html nginx
# 此时 docker volume ls 会看到一长串随机字符命名的数据卷
# 创建容器时指定命名数据卷(推荐)
docker run -d -p 80:80 \
--mount type=volume,source=for_nginx,target=/usr/share/nginx/html \
nginx建议
始终使用命名数据卷(Named Volume),避免使用匿名数据卷。匿名数据卷不易管理,容器删除后不会自动清理,可能导致磁盘空间浪费。可以使用 docker volume prune 命令清理未使用的匿名数据卷。
使用 Volume 启动容器
# 使用 --mount 语法(推荐)
docker run -d \
-p 80:80 \
--mount type=volume,source=for_nginx,target=/usr/share/nginx/html \
nginx
# 使用 -v 语法(简写)
docker run -d \
-p 80:80 \
-v for_nginx:/usr/share/nginx/html \
nginx验证数据持久化
# 在宿主机数据卷目录中创建测试文件
echo "<h1>Hello from Docker Volume</h1>" > /var/lib/docker/volumes/for_nginx/_data/test.html
# 进入容器验证文件是否存在
docker exec -it <container_id> /bin/bash
cat /usr/share/nginx/html/test.html
# 输出:<h1>Hello from Docker Volume</h1>
# 浏览器访问 http://<宿主机IP>/test.html 可以看到页面
# 强制删除容器
docker rm -f <container_id>
# 数据卷不会被删除,仍然保留在宿主机
docker volume ls | grep for_nginx
# 重新启动容器,数据仍然存在
docker run -d -p 80:80 \
-v for_nginx:/usr/share/nginx/html \
nginxVolume 的备份与恢复
# 备份 Volume
docker run --rm \
-v for_nginx:/source \
-v $(pwd):/backup \
alpine tar czf /backup/nginx_volume_backup.tar.gz /source
# 恢复 Volume(先创建新 Volume)
docker volume create for_nginx_restored
docker run --rm \
-v for_nginx_restored:/target \
-v $(pwd):/backup \
alpine tar xzf /backup/nginx_volume_backup.tar.gz -C /
# 清理不再使用的 Volume
docker volume prune多容器共享 Volume
# 创建共享数据卷
docker volume create shared_data
# 容器 A 写入数据
docker run -d --name container_a \
-v shared_data:/shared \
alpine sh -c "echo 'Hello from Container A' > /shared/data.txt"
# 容器 B 读取数据
docker run --rm \
-v shared_data:/shared \
alpine cat /shared/data.txt
# 输出:Hello from Container ABind Mounts(绑定挂载)
什么是 Bind Mount
Bind Mount 将宿主机上的任意目录或文件直接挂载到容器内部。与 Volume 不同,Bind Mount 的存储位置和生命周期完全由用户控制,Docker 不会自动管理这些文件。
基本操作
# 创建宿主机目录
mkdir -p /opt/nginx_html
# 使用 --mount 语法
docker run -d -p 80:80 \
--mount type=bind,source=/opt/nginx_html,target=/usr/share/nginx/html \
nginx
# 使用 -v 语法
docker run -d -p 80:80 \
-v /opt/nginx_html:/usr/share/nginx/html \
nginxBind Mount 的覆盖行为
# 注意:如果宿主机目录为空,挂载到容器中非空目录时,容器中该目录的文件会被"隐藏"
# 容器访问这个目录时,看到的文件均来自宿主机目录
# 示例:
mkdir -p /opt/nginx_html # 空目录
docker run -d -p 80:80 \
-v /opt/nginx_html:/usr/share/nginx/html \
nginx
# 进入容器查看,发现没有 nginx 默认的 index.html
docker exec -it <container_id> /bin/bash
ls /usr/share/nginx/html/
# 目录为空!
# 但浏览器可以正常显示 Welcome to nginx!
# 这是因为 nginx 的默认页面文件被宿主机的空目录覆盖了
# 解决方案:先拷贝容器默认文件到宿主机
docker run --name nginx_temp -d nginx
docker cp nginx_temp:/usr/share/nginx/html/ /opt/nginx_html/
docker stop nginx_temp && docker rm nginx_temp
# 然后再挂载
docker run -d -p 80:80 \
-v /opt/nginx_html:/usr/share/nginx/html \
nginx重要提醒
使用 Bind Mounts 挂载宿主机目录到容器中的非空目录时,容器中该目录的原始文件会被隐藏,容器只能看到宿主机目录中的文件。这在部署 Nginx、MySQL 等服务时需要特别注意。
挂载单个文件
# 挂载单个配置文件
docker run -d \
-v /etc/localtime:/etc/localtime:ro \
-v /opt/app/config.json:/app/config.json:ro \
myapp
# :ro 表示只读挂载,容器不能修改宿主机文件tmpfs Mounts(临时挂载)
什么是 tmpfs Mount
tmpfs Mount 将数据存储在宿主机的内存中(/dev/shm),而不是写入宿主机的文件系统。当容器停止时,tmpfs 中的数据会自动清除。这种方式适用于存储敏感信息或临时数据。
基本操作
# 使用 --mount 语法
docker run -d \
--mount type=tmpfs,target=/usr/share/nginx/html \
nginx
# 指定 tmpfs 大小限制
docker run -d \
--mount type=tmpfs,target=/tmp,tmpfs-size=100m,tmpfs-mode=1777 \
nginx
# 使用 --tmpfs 语法(简写)
docker run -d \
--tmpfs /tmp:rw,size=100m \
nginx验证临时数据特性
# 运行容器并绑定临时卷
docker run -d --name tmpfs_test \
--mount type=tmpfs,target=/app/data \
alpine sh -c "while true; do sleep 3600; done"
# 进入容器,创建文件
docker exec -it tmpfs_test sh
echo "test data" > /app/data/test.txt
cat /app/data/test.txt
# 输出:test data
# 删除容器重新创建
docker rm -f tmpfs_test
docker run -d --name tmpfs_test2 \
--mount type=tmpfs,target=/app/data \
alpine
# 查看数据,发现文件已丢失
docker exec tmpfs_test2 cat /app/data/test.txt
# cat: can't open '/app/data/test.txt': No such file or directory三种存储方式对比与适用场景
详细对比
| 特性 | Volume | Bind Mount | tmpfs Mount |
|---|---|---|---|
| 存储位置 | /var/lib/docker/volumes/ | 宿主机任意位置 | 内存(RAM) |
| 管理者 | Docker | 用户 | Docker |
| 容器删除后数据 | 保留 | 保留 | 丢失 |
| 性能 | 高 | 高 | 最高 |
| 可移植性 | 好 | 差 | 不适用 |
| 支持加密 | 可以(插件) | 不支持 | 内存即安全 |
| 共享给多个容器 | 支持 | 支持 | 不支持 |
适用场景总结
Volume 适用场景:
- 多个运行容器之间需要共享数据
- Docker 主机不能确保具有给定的目录或文件结构时
- 备份、恢复或将数据从一个 Docker 主机迁移到另一个 Docker 主机
- 数据库数据文件(MySQL、MongoDB、PostgreSQL 等)
- 需要远程存储的场景(NFS、S3 等存储驱动插件)
Bind Mount 适用场景:
- 主机与容器之间共享配置文件(如 Nginx 配置、应用配置)
- Docker 默认通过此方式为容器提供 DNS 解析(
/etc/resolv.conf) - 共享源代码或构建产物(如将 Maven 的
target/目录挂载到容器中) - 开发环境中实时同步代码变更
- 宿主机的文件或目录结构与容器需要一致时
tmpfs Mount 适用场景:
- 不想将数据存储在主机上,也不想存储在容器中
- 存储敏感数据(如临时密码、加密密钥)
- 应用需要写入大量非持久性的状态数据,需要保护容器性能
- 需要极高性能的临时读写操作
数据卷高级管理
数据卷驱动插件
# 使用 NFS 存储驱动创建 Volume
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/data/nfs \
nfs_volume
# 使用 Volume 启动容器
docker run -d \
-v nfs_volume:/data \
myapp
# 查看支持的数据卷驱动
docker volume ls -f driver=local数据卷容器(已废弃但仍有参考价值)
# 创建数据卷容器
docker create -v /data --name data_container busybox
# 使用 --volumes-from 引用数据卷容器的挂载
docker run -d --volumes-from data_container --name app1 myapp
docker run -d --volumes-from data_container --name app2 myapp数据卷的清理策略
# 查看所有数据卷
docker volume ls
# 查看未使用的数据卷
docker volume ls -f dangling=true
# 删除指定数据卷
docker volume rm for_nginx
# 清理所有未使用的数据卷
docker volume prune
# 强制清理(包括命名数据卷)
docker volume prune -a
# 查看数据卷占用的磁盘空间
docker system df -v常见问题与排错
数据卷权限问题
# 问题:容器内进程没有权限写入挂载目录
# 解决方案1:修改宿主机目录权限
chmod -R 777 /opt/nginx_html
# 解决方案2:在容器内指定用户
docker run -d \
-v /opt/app:/app \
--user 1000:1000 \
myapp
# 解决方案3:使用 --privileged(谨慎使用)
docker run -d \
-v /opt/app:/app \
--privileged \
myapp数据卷占用磁盘空间
# 查看 Docker 磁盘使用情况
docker system df
# 查看详细占用
docker system df -v
# 清理未使用的资源(数据卷、镜像、网络、容器)
docker system prune
# 仅清理未使用的数据卷
docker volume pruneWindows 挂载路径问题
# Windows Docker Desktop 中挂载路径格式
# 使用 /c/ 代替 C:\
docker run -d \
-v /c/Users/admin/data:/app/data \
myapp
# 或者使用 Docker Desktop 设置中的 File SharingDocker Compose 中的持久化
完整的多服务持久化配置
# docker-compose.yml — 多服务持久化示例
version: '3.8'
services:
# Nginx — 配置文件用 Bind Mount,日志用 Volume
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
# Bind Mount — 配置文件(只读)
- type: bind
source: ./nginx/conf.d
target: /etc/nginx/conf.d
read_only: true
# Bind Mount — SSL 证书
- type: bind
source: ./nginx/ssl
target: /etc/nginx/ssl
read_only: true
# Named Volume — 日志
- nginx_logs:/var/log/nginx
# tmpfs — 临时缓存
- type: tmpfs
target: /var/cache/nginx
tmpfs:
size: 100M
# MySQL — 数据用 Volume,配置用 Bind Mount
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password
volumes:
# Named Volume — 数据持久化(最重要)
- mysql_data:/var/lib/mysql
# Bind Mount — 初始化脚本
- type: bind
source: ./mysql/init
target: /docker-entrypoint-initdb.d
read_only: true
# Bind Mount — 自定义配置
- type: bind
source: ./mysql/my.cnf
target: /etc/mysql/conf.d/my.cnf
read_only: true
secrets:
- db_password
# Redis — 数据用 Volume,临时数据用 tmpfs
redis:
image: redis:7-alpine
command: redis-server /usr/local/etc/redis/redis.conf
volumes:
# Named Volume — RDB/AOF 持久化文件
- redis_data:/data
# Bind Mount — 配置文件
- type: bind
source: ./redis/redis.conf
target: /usr/local/etc/redis/redis.conf
read_only: true
# tmpfs — 临时缓存(高性能)
- type: tmpfs
target: /tmp
tmpfs:
size: 50M
# 应用 — 日志和上传文件持久化
app:
image: myapp:v1.0
volumes:
# Named Volume — 用户上传文件
- app_uploads:/app/uploads
# Named Volume — 应用日志
- app_logs:/app/logs
# Bind Mount — 配置文件
- type: bind
source: ./app/appsettings.json
target: /app/appsettings.json
read_only: true
depends_on:
- mysql
- redis
volumes:
mysql_data:
driver: local
redis_data:
driver: local
nginx_logs:
driver: local
app_uploads:
driver: local
app_logs:
driver: local
secrets:
db_password:
file: ./secrets/db_password.txt数据迁移与灾备
跨主机数据迁移
# 场景:将数据从旧服务器迁移到新服务器
# 方法 1:通过 tar 包迁移
# 1. 在旧服务器备份
docker run --rm \
-v mysql_data:/source:ro \
-v /backup:/backup \
alpine tar czf /backup/mysql_data.tar.gz -C /source .
# 2. 传输到新服务器
scp /backup/mysql_data.tar.gz newserver:/backup/
# 3. 在新服务器恢复
docker volume create mysql_data
docker run --rm \
-v mysql_data:/target \
-v /backup:/backup:ro \
alpine tar xzf /backup/mysql_data.tar.gz -C /target
# 方法 2:通过管道直接传输(适合大数据量)
docker run --rm \
-v mysql_data:/source:ro \
alpine tar cf - -C /source . |
ssh newserver 'docker run --rm -i \
-v mysql_data:/target \
alpine tar xf - -C /target'
# 方法 3:使用 rsync 直接同步 Volume 目录
# 注意:需要确保没有容器正在使用该 Volume
rsync -avz /var/lib/docker/volumes/mysql_data/_data/ \
newserver:/var/lib/docker/volumes/mysql_data/_data/数据库热备份
# MySQL 热备份(不停止服务)
docker exec mysql_container \
mysqldump -u root -p$MYSQL_ROOT_PASSWORD \
--all-databases --single-transaction \
--quick --lock-tables=false \
> /backup/mysql_$(date +%Y%m%d_%H%M%S).sql
# 恢复 MySQL 备份
docker exec -i mysql_container \
mysql -u root -p$MYSQL_ROOT_PASSWORD \
< /backup/mysql_20260415_120000.sql
# PostgreSQL 热备份
docker exec postgres_container \
pg_dumpall -U postgres \
> /backup/postgres_$(date +%Y%m%d_%H%M%S).sql
# MongoDB 备份
docker exec mongo_container \
mongodump --archive=/data/backup/mongo.dump
docker cp mongo_container:/data/backup/mongo.dump ./mongo_$(date +%Y%m%d).dump
# Redis 备份(触发 BGSAVE 后拷贝 RDB 文件)
docker exec redis_container redis-cli BGSAVE
docker cp redis_container:/data/dump.rdb ./redis_$(date +%Y%m%d).rdbVolume 性能优化
存储驱动与性能
# 查看 Docker 使用的存储驱动
docker info | grep "Storage Driver"
# 常见存储驱动性能对比:
# overlay2 — 默认推荐,性能最优
# devicemapper — 旧版本使用,性能较差
# btrfs — 支持写时复制,适合大量小文件
# Volume 性能优化建议:
# 1. 使用 Volume 而非 Bind Mount(更好的 I/O 性能)
# 2. 数据库数据放在专用磁盘上
# 3. 使用 tmpfs 存储临时数据(内存级别性能)
# 4. 减少 Bind Mount 的文件数量(每个挂载有开销)
# 创建 Volume 时指定存储选项
docker volume create \
--driver local \
--opt type=tmpfs \
--opt device=tmpfs \
--opt o=size=1g \
fast_cache
# 使用 NFS Volume 实现多主机共享
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,nfsvers=4,rw \
--opt device=:/data/shared \
nfs_sharedVolume 监控与告警
# 监控 Volume 的磁盘使用情况
# 1. 查看 Docker 整体磁盘使用
docker system df -v
# 2. 查看特定 Volume 的大小
docker volume inspect mysql_data --format '{{.Mountpoint}}'
sudo du -sh /var/lib/docker/volumes/mysql_data/_data
# 3. 批量检查所有 Volume 大小
for vol in $(docker volume ls -q); do
size=$(sudo du -sh /var/lib/docker/volumes/${vol}/_data 2>/dev/null | cut -f1)
echo "$vol: $size"
done
# 4. 设置磁盘空间告警
# 在 crontab 中添加:
# */5 * * * * df -h /var/lib/docker | awk 'NR==2 && int($5) > 80 {print "Docker disk usage: "$5 | "mail -s alert admin@example.com"}'最佳实践
- 优先使用 Volume:除非需要直接修改宿主机文件,否则优先使用 Volume
- 命名数据卷:始终使用命名数据卷,便于管理和维护
- 分离关注点:配置文件用 Bind Mount,数据文件用 Volume,临时数据用 tmpfs
- 定期清理:使用
docker volume prune清理未使用的数据卷 - 备份策略:定期备份重要的 Volume 数据
- 只读挂载:配置文件使用
:ro只读挂载,防止容器意外修改 - 权限管理:注意容器用户与宿主机文件权限的映射关系
- 监控空间:监控数据卷占用的磁盘空间,避免磁盘满
