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。