AI 摘要
AI
正在生成摘要...

Docker 数据持久化说明:Volume、Bind Mount 与 tmpfs 的正确打开方式

说明: 本文为配置思路与示例整理,不代表作者已在自己的服务器上逐项验证全部命令。执行涉及公网暴露、账户权限、数据删除或服务重启的操作前,请先备份,并结合官方文档与实际环境核验。

容器删了,数据也没了?这是 Docker 新手最常踩的坑之一。本文将带你深入理解 Docker 的三种数据持久化方案,掌握生产环境的建议。

问题描述

你部署了一个 MySQL 容器,跑了一段时间,数据都在里面。突然有一天,你执行了:

BASH
docker rm my-mysql

然后发现——所有数据都没了。

BASH
docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
# ... 使用了一段时间 ...
docker rm -f my-mysql
# 数据全没了!

这不是 Docker 的 bug,而是 Docker 的设计哲学:容器是临时的、可替换的。容器内部的文件系统(Container Layer)在容器删除时会被一并清除。

原因分析

Docker 的分层文件系统

理解数据持久化之前,需要先理解 Docker 的存储机制:

TEXT
┌─────────────────────────────────┐
│        Container Layer          │  ← 可写层(容器删除即丢失)
├─────────────────────────────────┤
│        Image Layer (N)          │  ← 只读层
├─────────────────────────────────┤
│        Image Layer (2)          │  ← 只读层
├─────────────────────────────────┤
│        Image Layer (1)          │  ← 只读层(基础镜像)
└─────────────────────────────────┘

当你 docker run 一个镜像时,Docker 会在镜像的只读层之上添加一个可写层(Container Layer)。你在容器内创建、修改的所有文件都写在这个可写层里。容器删除时,这个可写层也会被删除。

这就是为什么需要数据持久化——把数据放在容器生命周期之外。

三种持久化方案对比

特性 Volume(数据卷) Bind Mount(绑定挂载) tmpfs(临时文件系统)
存储位置 Docker 管理的目录 宿主机指定目录 内存
数据持久性 ✅ 容器删除后保留 ✅ 容器删除后保留 ❌ 容器停止即丢失
性能 较好 较好 最快(内存速度)
可移植性 ✅ 跨平台 ⚠️ 依赖宿主机路径 ⚠️ 依赖宿主机配置
备份难度 中等 简单 不可备份
适用场景 数据库、持久数据 开发环境、配置文件 敏感数据、缓存

解决方案

方案一:Volume(数据卷)—— 生产环境首选

Volume 是 Docker 官方推荐的持久化方式,由 Docker 引擎完全管理。

创建和使用 Volume:

BASH
# 创建一个命名卷
docker volume create mysql-data

# 查看卷信息
docker volume inspect mysql-data
# {
#     "CreatedAt": "2026-05-10T10:00:00+08:00",
#     "Driver": "local",
#     "Labels": {},
#     "Mountpoint": "/var/lib/docker/volumes/mysql-data/_data",
#     "Name": "mysql-data",
#     "Options": {},
#     "Scope": "local"
# }

# 使用卷启动容器
docker run -d \
  --name my-mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

# 验证数据持久化
docker exec my-mysql mysql -uroot -p123456 -e "CREATE DATABASE testdb;"
docker rm -f my-mysql

# 重新启动,数据还在!
docker run -d \
  --name my-mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

docker exec my-mysql mysql -uroot -p123456 -e "SHOW DATABASES;"
# testdb 还在!

Volume 管理命令:

BASH
# 列出所有卷
docker volume ls

# 查看卷详细信息
docker volume inspect mysql-data

# 删除未使用的卷
docker volume prune

# 删除指定卷(需先停止使用它的容器)
docker volume rm mysql-data

Docker Compose 中使用 Volume:

YAML
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data

volumes:
  mysql-data:
  redis-data:

方案二:Bind Mount(绑定挂载)—— 开发环境利器

Bind Mount 将宿主机的指定目录直接挂载到容器内,适合开发环境和需要直接访问宿主机文件的场景。

BASH
# 将当前目录挂载到容器的 /app 目录
docker run -d \
  --name my-app \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/config:/app/config:ro \
  node:18-alpine \
  node /app/src/server.js

# :ro 表示只读挂载,容器内不能修改宿主机文件

Docker Compose 中使用 Bind Mount:

YAML
services:
  web:
    image: node:18-alpine
    volumes:
      - ./src:/app/src          # 源代码热重载
      - ./config:/app/config:ro # 配置文件只读
    ports:
      - "3000:3000"

⚠️ 常见坑点:权限问题

BASH
# Linux 上:宿主机用户 UID 与容器内用户不匹配
# 容器内 node 用户 UID 是 1000
# 宿主机当前用户 UID 可能不是 1000

# 解决方案 1:在 Dockerfile 中设置用户
FROM node:18-alpine
RUN addgroup -g 1000 appgroup && adduser -u 1000 -G appgroup -D appuser
USER appuser

# 解决方案 2:运行时指定用户
docker run --user $(id -u):$(id -g) ...

# 解决方案 3:修改宿主机目录权限
chmod -R 777 ./src

方案三:tmpfs —— 敏感数据的安全港

tmpfs 挂载将数据存储在内存中,容器停止后数据消失。适合存储密码、密钥等敏感信息。

BASH
# 将敏感数据存储在内存中
docker run -d \
  --name my-app \
  --tmpfs /app/secrets:rw,noexec,nosuid \
  my-app:latest

# :rw - 可读写
# :noexec - 禁止执行(安全)
# :nosuid - 忽略 SUID 位(安全)

Docker Compose 中使用 tmpfs:

YAML
services:
  app:
    image: my-app:latest
    tmpfs:
      - /app/secrets:rw,noexec,nosuid
      - /tmp:size=100M

生产环境建议

1. 数据库容器必须使用 Volume

YAML
# ✅ 正确:使用命名卷
services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: ${DB_PASSWORD}  # 从 .env 文件读取
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres-data:
    driver: local

2. 配置文件使用 Bind Mount + 只读

YAML
# ✅ 配置文件只读挂载
services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./conf.d:/etc/nginx/conf.d:ro
      - nginx-cache:/var/cache/nginx

volumes:
  nginx-cache:

3. 实现备份策略

BASH
# 方法 1:直接备份 Volume 数据
docker run --rm \
  -v mysql-data:/data:ro \
  -v $(pwd)/backup:/backup \
  alpine \
  tar czf /backup/mysql-data-$(date +%Y%m%d).tar.gz -C /data .

# 方法 2:使用 docker cp 从运行中的容器备份
docker exec my-mysql \
  mysqldump -uroot -p123456 --all-databases > backup.sql

# 方法 3:使用第三方工具(如 restic)增量备份
docker run --rm \
  -v mysql-data:/data:ro \
  -v backup-repo:/backup \
  restic/restic backup /data

4. 清理磁盘空间

BASH
# 查看 Docker 磁盘使用情况
docker system df

# 清理未使用的 Volume
docker volume prune

# 更激进的清理(⚠️ 谨慎使用)
docker system prune -a --volumes

# 查找大 Volume
docker system df -v

5. 使用 Docker Compose 管理多服务持久化

YAML
# 较完整的 docker-compose.yml 示例
services:
  postgres:
    image: postgres:15-alpine
    restart: always
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:7-alpine
    restart: always
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data

  app:
    image: my-app:latest
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./uploads:/app/uploads
      - app-logs:/app/logs

volumes:
  postgres-data:
  redis-data:
  app-logs:

验证清单

部署完成后,用以下命令验证数据持久化是否生效:

BASH
# 1. 检查 Volume 是否创建
docker volume ls | grep mysql-data

# 2. 检查容器挂载点
docker inspect my-mysql --format='{{json .Mounts}}' | jq

# 3. 测试数据持久化
#    a. 写入数据
docker exec my-mysql mysql -uroot -p123456 -e "CREATE TABLE test (id INT);"
#    b. 删除容器
docker rm -f my-mysql
#    c. 重新创建容器(使用相同 Volume)
docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD=123456 -v mysql-data:/var/lib/mysql mysql:8.0
#    d. 验证数据还在
docker exec my-mysql mysql -uroot -p123456 -e "SHOW TABLES FROM testdb;"

# 4. 检查磁盘使用
docker system df -v

常见问题排查

Q1: 容器启动报 "permission denied"

BASH
# 原因:Volume 目录权限与容器内用户不匹配
# 解决:修改目录权限
sudo chown -R 999:999 /path/to/volume  # PostgreSQL 容器用户 UID 是 999

Q2: Bind Mount 在 macOS/Windows 上性能差

BASH
# 原因:Docker Desktop 使用虚拟机,文件系统同步有开销
# 解决:对性能敏感的目录使用 Volume 代替 Bind Mount
# 或者使用 :cached / :delegated 标记(Docker Desktop)
-v $(pwd)/src:/app/src:cached

Q3: Volume 数据意外丢失

BASH
# 原因:使用了 docker system prune 或 docker volume prune
# 预防:命名 Volume 不会被 prune 默认删除
# 恢复:定期备份是唯一可靠方案

总结

场景 推荐方案 理由
数据库数据 Volume 可靠、性能好、Docker 原生支持
开发环境代码 Bind Mount 方便实时编辑和调试
配置文件 Bind Mount + :ro 防止容器意外修改
敏感信息 tmpfs 内存存储,安全可靠
日志文件 Volume 持久化且便于收集

记住一个原则:容器是临时的,数据是永久的。在设计任何 Docker 部署方案时,第一步就应该考虑数据如何持久化。


本文是 Docker 系列教程的第 5 篇。前几篇涵盖了 Docker Compose、环境变量、跨架构部署等内容,欢迎回看。

评论