Docker 数据持久化
一、为什么需要数据持久化
1.1 容器存储的特点
Docker 容器默认是无状态的:
| 特点 | 说明 | 影响 |
|---|---|---|
| 临时性 | 容器删除后,内部数据全部丢失 | 数据库、日志、配置文件会丢失 |
| 隔离性 | 容器间文件系统相互隔离 | 无法共享数据 |
| 写时复制 | 容器层对镜像层只读 | 每次修改产生新层,性能损耗 |
1.2 需要持久化的数据
| 数据类型 | 示例 | 为什么需要持久化 |
|---|---|---|
| 应用数据 | 数据库文件、用户上传文件 | 容器删除数据必须保留 |
| 配置文件 | nginx.conf、application.yml | 容器更新配置不丢失 |
| 日志文件 | /var/log/app/*.log | 容器删除后日志仍需保留 |
| 缓存数据 | Redis 持久化文件、临时文件 | 重启后数据不丢失 |
1.3 数据持久化方案对比
Docker 提供三种数据持久化方式:
| 方式 | 管理方式 | 性能 | 适用场景 |
|---|---|---|---|
| Volume(卷) | Docker 管理 | 高 | 生产环境推荐 |
| Bind Mount(绑定挂载) | 用户管理 | 高 | 开发调试、配置文件 |
| tmpfs | 内存 | 最高 | 临时缓存、敏感数据 |
二、Volume(卷)
tips: 生产环境推荐
2.1 什么是 Volume
Volume 是 Docker 管理的独立于容器生命周期的数据存储单元,存储在宿主机 /var/lib/docker/volumes/ 目录下。
优点:
- Docker 完全管理,备份/迁移方便
- 性能好(直接读写宿主机文件系统)
- 支持卷驱动(NFS、云存储等)
- 多个容器可共享一个 Volume
2.2 Volume 基本操作
# ========== 创建 Volume ==========
docker volume create my-data
# 查看所有 Volume
docker volume ls
# 查看 Volume 详情
docker volume inspect my-data
# 删除 Volume
docker volume rm my-data
# 清理未使用的 Volume
docker volume prune
# ========== 运行容器时挂载 Volume ==========
# 挂载已存在的 Volume
docker run -d -v my-data:/app/data --name app myimage
# 挂载时自动创建 Volume
docker run -d -v myapp-data:/app/data --name app myimage
# 只读挂载
docker run -d -v my-data:/app/data:ro --name app myimage
2.3 Volume 完整示例
# 1. 创建 Volume
docker volume create postgres-data
# 2. 运行 PostgreSQL 容器,挂载 Volume
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
# 3. 查看 Volume 位置
docker volume inspect postgres-data
# Mountpoint: /var/lib/docker/volumes/postgres-data/_data
# 4. 删除容器,数据保留
docker rm -f postgres
# 5. 创建新容器,挂载同一个 Volume(数据恢复)
docker run -d \
--name postgres-new \
-e POSTGRES_PASSWORD=secret \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
# 数据完整保留!
2.4 常用 Volume 驱动
# 使用 NFS 驱动(需安装插件)
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/path/nfs \
nfs-volume
# 使用 S3 驱动
docker volume create \
--driver s3 \
--opt bucket=my-bucket \
--opt endpoint=...
s3-volume
2.5 Docker Compose 中使用 Volume
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
app:
image: myapp
volumes:
- app-logs:/app/logs
volumes:
postgres-data: # 命名卷(Docker 管理)
app-logs: # 命名卷(Docker 管理)
三、Bind Mount(绑定挂载)
3.1 什么是 Bind Mount
Bind Mount 将宿主机指定目录或文件挂载到容器内,容器对挂载目录的修改会直接反映到宿主机。
优点:
- 开发调试方便(代码实时同步)
- 宿主机文件直接访问
缺点:
- 依赖宿主机目录结构,可移植性差
- 安全风险(容器可修改宿主机文件)
3.2 基本用法
# 挂载目录:-v 宿主机路径:容器路径
docker run -d \
-v /home/user/app:/app \
--name dev \
node:18
# 挂载单个文件
docker run -d \
-v /home/user/config/nginx.conf:/etc/nginx/nginx.conf:ro \
--name nginx \
nginx
# 使用 --mount(更清晰)
docker run -d \
--mount type=bind,source=/home/user/app,target=/app \
node:18
# 只读挂载
docker run -d \
-v /home/user/config:/app/config:ro \
myapp
3.3 开发环境典型用法
# docker-compose.dev.yml
version: '3.8'
services:
app:
build: .
volumes:
# 代码目录映射,修改代码实时生效
- ./src:/app/src
# 配置文件映射
- ./config:/app/config
# 只读挂载,防止容器修改依赖
- ./package.json:/app/package.json:ro
ports:
- "3000:3000"
command: npm run dev
3.4 Bind Mount 权限问题
# 问题:容器内用户无法写入挂载目录
# 解决方案1:指定 uid/gid
docker run -d \
--user $(id -u):$(id -g) \
-v /home/user/app:/app \
node:18
# 解决方案2:容器内调整权限
docker run -d \
-v /home/user/app:/app \
node:18 \
sh -c "chown -R node:node /app && npm start"
四、tmpfs(内存文件系统)
4.1 什么是 tmpfs
tmpfs 将数据存储在宿主机内存中,容器停止/删除后数据自动清除。
优点:
- 读写速度极快
- 容器删除自动清理
- 敏感数据不落盘
缺点:
- 数据重启丢失
- 内存占用
4.2 基本用法
# 挂载 tmpfs
docker run -d \
--tmpfs /app/cache \
--name app \
myapp
# 带参数挂载
docker run -d \
--tmpfs /app/cache:rw,size=100M,uid=1000 \
myapp
# 限制内存使用
docker run -d \
--memory=2g \
--tmpfs /app/cache:size=500M \
myapp
4.3 适用场景
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx
tmpfs:
- /tmp/nginx:rw,size=64M
- /var/cache/nginx:rw,size=128M
volumes:
- nginx-logs:/var/log/nginx
| 场景 | 说明 |
|---|---|
| 临时缓存 | 应用缓存数据,重启可重建 |
| Session 存储 | 用户会话(配合 Redis) |
| 临时上传目录 | 文件处理临时存放 |
| 敏感凭证 | API Key、Token 不落盘 |
五、三种方式对比
| 对比项 | Volume | Bind Mount | tmpfs |
|---|---|---|---|
| 数据存储位置 | /var/lib/docker/volumes/ | 用户指定路径 | 内存 |
| Docker 管理 | 完全管理 | 用户管理 | 自动清理 |
| 容器删除后数据 | 保留 | 保留 | 清除 |
| 宿主机直接访问 | 需 root 权限 | 直接访问 | 无法访问 |
| 性能 | 高 | 高 | 最高 |
| 可移植性 | 好(卷名迁移) | 差(路径依赖) | N/A |
| 适用场景 | 生产数据 | 开发调试 | 临时缓存 |
选择决策树
是否需要数据持久化?
├── 否 → tmpfs(临时缓存/敏感数据)
└── 是
├── 是否需要宿主机直接访问/编辑?
│ ├── 是 → Bind Mount(开发/配置文件)
│ └── 否 → Volume(生产推荐)
└── 是否需要跨宿主机共享?
└── 是 → Volume Driver(NFS/云存储)
六、数据备份与迁移
6.1 备份 Volume
# 方法1:使用临时容器打包 Volume 数据
docker run --rm \
-v my-data:/source \
-v $(pwd):/backup \
alpine \
tar -czf /backup/my-data-backup.tar.gz -C /source .
# 方法2:直接复制 Volume 目录(需 root)
sudo tar -czf my-data-backup.tar.gz /var/lib/docker/volumes/my-data/_data
# 方法3:使用官方容器
docker run --rm \
-v my-data:/data \
-v $(pwd):/backup \
alpine \
tar -czf /backup/my-data-$(date +%Y%m%d).tar.gz /data
6.2 恢复 Volume
# 创建新的 Volume
docker volume create my-data-restored
# 恢复数据
docker run --rm \
-v my-data-restored:/target \
-v $(pwd):/backup \
alpine \
tar -xzf /backup/my-data-backup.tar.gz -C /target
# 验证
docker run --rm -v my-data-restored:/data alpine ls -la /data
6.3 多容器共享 Volume
# 创建共享 Volume
docker volume create shared-data
# 多个容器挂载同一个 Volume
docker run -d --name writer -v shared-data:/data alpine sh -c "while true; do echo $(date) >> /data/log.txt; sleep 1; done"
docker run -d --name reader -v shared-data:/data alpine tail -f /data/log.txt
# 查看共享数据
docker exec reader cat /data/log.txt
6.4 使用卷驱动实现跨宿主机共享
# 使用 NFS 作为 Volume 后端
version: '3.8'
services:
app:
image: myapp
volumes:
- nfs-data:/app/data
volumes:
nfs-data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.100,rw,nfsvers=4
device: :/exports/data
七、Volume 生命周期管理
7.1 Volume 生命周期
创建(docker volume create)
↓
挂载到容器(-v / --mount)
↓
容器读写数据
↓
容器删除(数据保留)
↓
Volume 继续存在,可挂载到新容器
↓
手动删除(docker volume rm)
7.2 清理孤儿 Volume
# 查看所有 Volume
docker volume ls
# 查看 Volume 使用情况
docker volume ls -f dangling=true # 虚悬 Volume(未被任何容器使用)
# 查看 Volume 详情
docker volume inspect <volume-name>
# 查看哪个容器在使用 Volume
docker ps -a --filter volume=<volume-name>
# 清理未使用的 Volume
docker volume prune
# 强制清理所有未使用 Volume(不提示)
docker volume prune -f
7.3 Volume 命名规范
| 环境 | 命名规范 | 示例 |
|---|---|---|
| 开发 | {项目名}-{服务名}-dev | myapp-postgres-dev |
| 测试 | {项目名}-{服务名}-test | myapp-postgres-test |
| 生产 | {项目名}-{服务名}-{环境} | myapp-postgres-prod |
八、Docker Compose Volume 高级配置
8.1 多种 Volume 类型组合
version: '3.8'
services:
app:
image: myapp
volumes:
# 命名卷(生产推荐)
- app-data:/app/data
# 绑定挂载(开发配置文件)
- ./config:/app/config:ro
# tmpfs(缓存)
- type: tmpfs
target: /app/cache
tmpfs:
size: 100M
# 扩展选项
- type: volume
source: app-logs
target: /app/logs
read_only: false
volume:
nocopy: true
volumes:
app-data:
driver: local
app-logs:
external: true
external_name: shared-logs-v1
8.2 外部 Volume
# 使用已存在的 Volume(不自动创建)
volumes:
existing-data:
external: true
external_name: postgres-data-v2 # 实际 Volume 名
8.3 带标签的 Volume
volumes:
prod-data:
driver: local
labels:
environment: production
team: backend
backup: daily
九、实战场景示例
9.1 MySQL 数据持久化
# 创建 Volume
docker volume create mysql-data
# 运行 MySQL
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=myapp \
-v mysql-data:/var/lib/mysql \
-v ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro \
mysql:8.0
# 备份
docker exec mysql mysqldump -uroot -psecret myapp > backup.sql
9.2 Nginx 静态文件 + 配置
version: '3.8'
services:
nginx:
image: nginx:alpine
volumes:
# 配置文件
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
# 静态资源
- ./static:/usr/share/nginx/html:ro
# 日志(持久化)
- nginx-logs:/var/log/nginx
# SSL 证书
- ./certs:/etc/nginx/certs:ro
ports:
- "80:80"
- "443:443"
volumes:
nginx-logs:
9.3 ELK 日志栈持久化
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.10
volumes:
- es-data:/usr/share/elasticsearch/data
environment:
- discovery.type=single-node
logstash:
image: logstash:8.10
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
- app-logs:/var/log/app:ro
volumes:
es-data:
driver: local
app-logs:
external: true
十、监控与维护
10.1 查看 Volume 占用
# 查看所有 Volume 大小
docker system df -v
# 查看具体 Volume 大小
docker run --rm -v my-data:/data alpine du -sh /data
# 查看 Volume 使用详情
docker volume inspect my-data | jq .[].UsageData
10.2 Volume 迁移
# 从旧 Volume 迁移到新 Volume
docker run --rm \
-v old-volume:/source \
-v new-volume:/target \
alpine \
sh -c "cp -a /source/. /target/"
十一、最佳实践总结
| 实践 | 说明 |
|---|---|
| 生产环境使用 Volume | 不要用 Bind Mount,用命名卷 |
| 明确 Volume 命名 | 使用 {项目}-{服务}-{环境} 格式 |
| 定期备份 | 使用定时任务备份重要 Volume |
| 定期清理 | 定期 docker volume prune 清理孤儿卷 |
| 敏感数据用 tmpfs | 密钥、Token 临时存储用内存文件系统 |
| 配置文件用 Bind Mount | 开发和配置管理场景 |
容器挂载时加 :ro | 配置文件、证书等只读挂载 |
| 不要挂载整个根目录 | 安全风险,只挂载具体子目录 |
十二、命令速查表
| 操作 | 命令 |
|---|---|
| 创建 Volume | docker volume create <name> |
| 查看所有 Volume | docker volume ls |
| 查看 Volume 详情 | docker volume inspect <name> |
| 删除 Volume | docker volume rm <name> |
| 清理未使用 Volume | docker volume prune |
| 运行容器挂载 Volume | docker run -v <volume>:/path <image> |
| 运行容器 Bind Mount | docker run -v /host:/container <image> |
| 运行容器 tmpfs | docker run --tmpfs /path <image> |
| 查看容器挂载 | docker inspect <container> \| jq .Mounts |
十三、总结
Docker 数据持久化三选一:
- Volume(生产推荐):Docker 管理,独立于容器,可备份迁移
Bind Mount(开发调试):宿主机路径映射,代码实时同步
tmpfs(临时缓存):内存存储,重启丢失,速度最快
黄金法则:生产环境用命名卷,开发调试用 Bind Mount,敏感缓存用 tmpfs。