Chapter 06

数据卷与持久化

掌握三种挂载方式,让容器化应用的数据安全持久地保存

为什么需要持久化

容器的设计哲学是无状态(Stateless)的:容器可以被随时创建、销毁、替换。当一个容器被删除时,它的可写层(Container Layer)也随之消失,其中写入的所有文件都会永久丢失。


# 演示数据丢失
docker run --name test-db mysql:8.0 -e MYSQL_ROOT_PASSWORD=root
# ... 写入数据 ...
docker rm -f test-db   # 删除容器,数据消失!

解决方案是将数据存储在容器之外的持久化存储中。Docker 提供三种挂载方式。

三种挂载方式对比

类型语法示例存储位置适用场景
Volume(命名卷) -v mydata:/var/lib/mysql Docker 管理(/var/lib/docker/volumes/ 数据库数据、需要备份的持久数据
Bind Mount(绑定挂载) -v /host/path:/container/path 宿主机任意路径 开发时代码热重载、挂载配置文件
tmpfs Mount --tmpfs /run 宿主机内存 敏感临时数据、高性能临时文件

存储位置对比图

宿主机文件系统
/var/lib/docker/volumes/(Volume 存储区域)
宿主机任意路径(Bind Mount 来源)
容器可写层(临时,随容器销毁)

Volume(命名卷)

Volume 是 Docker 推荐的持久化方式。由 Docker 完全管理,与宿主机的具体目录结构解耦。

Volume 操作命令


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

# 列出所有卷
docker volume ls

# 查看卷详情(存储路径、挂载点)
docker volume inspect mysql-data

# 删除指定卷
docker volume rm mysql-data

# 清理所有未使用的卷(危险!会丢失数据)
docker volume prune

使用 Volume 挂载(-v 语法)


# 命名卷:卷名:容器路径
docker run -d \
  --name my-mysql \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8.0

# 如果 mysql-data 卷不存在,Docker 自动创建

使用 --mount 语法(推荐,更清晰)


docker run -d \
  --name my-mysql \
  --mount type=volume,source=mysql-data,target=/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8.0
💡

-v vs --mount-v 语法简洁但容易混淆(命名卷 vs 绑定挂载取决于路径是否以 / 开头)。--mount 语法更明确,推荐在脚本和 Compose 中使用。

Bind Mount(绑定挂载)

将宿主机上的具体文件或目录挂载到容器内。主要用于开发场景,让容器实时看到宿主机上的代码修改。


# 将当前目录挂载到容器(开发热重载)
docker run -d \
  --name dev-server \
  -v $(pwd):/app \
  -p 3000:3000 \
  node:18-alpine \
  sh -c "npm install && npm run dev"

# 挂载单个配置文件
docker run -d \
  -v /host/nginx.conf:/etc/nginx/nginx.conf:ro \  # :ro = 只读
  nginx:1.25

只读挂载


# -v 语法:在路径后加 :ro
docker run -v /config:/etc/app:ro myapp

# --mount 语法:添加 readonly 选项
docker run --mount type=bind,source=/config,target=/etc/app,readonly myapp
⚠️

Bind Mount 的注意事项

tmpfs Mount

数据存储在宿主机内存中,容器停止时自动清除。适合存储敏感临时数据(如密钥、Session Token)。


# 挂载 tmpfs 到 /run 目录
docker run --tmpfs /run \
  --tmpfs /tmp:rw,size=64m \
  myapp

# --mount 语法
docker run --mount type=tmpfs,target=/tmp,tmpfs-size=64m myapp

数据备份与恢复

Volume 备份的常用技巧:使用一个临时容器同时挂载数据卷和宿主机备份目录,执行 tar 打包:


# 备份:将 mysql-data 卷打包到当前目录
docker run --rm \
  -v mysql-data:/source:ro \
  -v $(pwd):/backup \
  busybox \
  tar czf /backup/mysql-backup-$(date +%Y%m%d).tar.gz -C /source .

# 恢复:从备份文件还原到新卷
docker volume create mysql-data-restored
docker run --rm \
  -v mysql-data-restored:/target \
  -v $(pwd):/backup \
  busybox \
  tar xzf /backup/mysql-backup-20241201.tar.gz -C /target

实战:MySQL 容器数据持久化

演示删除容器再重新创建后数据仍然存在:


# 1. 创建命名卷
docker volume create mysql-persistent

# 2. 启动 MySQL 并挂载卷
docker run -d \
  --name mysql-demo \
  -v mysql-persistent:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=mypassword \
  -e MYSQL_DATABASE=testdb \
  -p 3306:3306 \
  mysql:8.0

# 3. 等待 MySQL 启动,写入数据
docker exec -it mysql-demo mysql -uroot -pmypassword testdb

# 在 MySQL 控制台内执行:
# CREATE TABLE users (id INT, name VARCHAR(50));
# INSERT INTO users VALUES (1, 'Alice');
# exit

# 4. 删除容器
docker rm -f mysql-demo

# 5. 重新创建容器,挂载同一个卷
docker run -d \
  --name mysql-demo-new \
  -v mysql-persistent:/var/lib/mysql \  # 同一个卷!
  -e MYSQL_ROOT_PASSWORD=mypassword \
  -p 3306:3306 \
  mysql:8.0

# 6. 数据还在!
docker exec -it mysql-demo-new mysql -uroot -pmypassword testdb
# SELECT * FROM users;  → 返回 1, Alice

本章小结 — Volume 是持久化生产数据的最佳选择;Bind Mount 适合开发时代码同步;tmpfs 适合敏感临时数据。在 Docker Compose 中声明 volumes,能使多服务共享数据变得简单。