为什么需要持久化
容器的设计哲学是无状态(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 的注意事项
- 路径必须是绝对路径(可用
$(pwd)获取当前目录) - 容器内的写入会直接修改宿主机文件(双向同步)
- 宿主机目录不存在时,Docker 会创建它(而不是报错)
- 性能(尤其在 macOS 上)不如 Volume,因为需要跨 VM 同步
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,能使多服务共享数据变得简单。