共计 6302 个字符,预计需要花费 16 分钟才能阅读完成。
文章目录[显示]
Day 17 Compose 数据卷与持久化
Docker 30 天实战系列 · Day 17
你有没有经历过这种心碎时刻:辛辛苦苦往 MySQL 里灌了几万条数据,结果一个 docker-compose down 之后,数据全没了?那种感觉,就像你花了一下午整理好的房间,被熊孩子五分钟给拆了。
容器天生就是 " 用完即弃 " 的,这是它的优点,也是它让人抓狂的地方。今天我们就来解决这个问题——让数据在容器的生死轮回中 " 永生 "。
本文你将学到
- Named Volumes 和 Bind Mounts 的区别与使用场景
- 如何在 Compose 中配置数据卷实现持久化
- MySQL 容器数据持久化的完整方案
- 数据卷的备份与恢复实操
- 生产环境数据卷的最佳实践
| 阅读时间 | 实操时间 | 难度等级 |
|---|---|---|
| 10 分钟 | 30 分钟 | 中级 |
一、容器数据的 " 前世今生 "
先搞清楚一个基本事实:容器的文件系统是临时的。每次容器重建,里面的数据就会从头开始。这就好比你住酒店——退房之后,房间会被打扫得干干净净,你放在床头柜上的东西不会给你留着。
那问题来了,数据库、上传的文件、配置信息这些东西怎么办?总不能每次重启都从零开始吧。
Docker 给了我们两种 " 保险箱 ":
┌──────────────────────────────────────────────────┐
│ 宿主机文件系统 │
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Named Volume │ │ Bind Mount │ │
│ │ │ │ │ │
│ │ Docker 管理 │ │ 你指定目录 │ │
│ │ /var/lib/ │ │ /home/data/ │ │
│ │ docker/ │ │ /app/config/ │ │
│ │ volumes/ │ │ │ │
│ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 容器内部文件系统 │ │
│ │ │ │
│ │ /var/lib/mysql /app/config │ │
│ └─────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
二、Named Volumes vs Bind Mounts
这两兄弟经常被搞混,我们用一个生活类比来理解。
Named Volumes(命名卷) 就像银行的保险柜。你把东西交给银行保管,银行给你一个编号,你不需要知道具体放在哪个抽屉里。Docker 帮你管理存储位置、权限、生命周期,你只管用就行。
Bind Mounts(绑定挂载) 就像你在家里买了个保险箱,放在你指定的位置。你清楚地知道东西在哪,可以直接打开看,但维护工作得你自己来。
| 对比维度 | Named Volumes | Bind Mounts |
|---|---|---|
| 存储位置 | Docker 管理(/var/lib/docker/volumes/) | 你指定的宿主机路径 |
| 可移植性 | 高,跨机器方便迁移 | 低,依赖宿主机目录结构 |
| 性能 | Linux 原生性能 | 取决于文件系统 |
| 权限管理 | Docker 自动处理 | 需要手动管理 |
| 适用场景 | 数据库、应用数据 | 开发时代码热更新、配置文件 |
| Compose 语法 | volume_name:/container/path |
./host/path:/container/path |
选择原则:生产环境优先用 Named Volumes,开发环境按需用 Bind Mounts。
三、Compose 中配置数据卷
3.1 Named Volumes 基础用法
# docker-compose.yml
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
MYSQL_DATABASE: myapp
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
volumes:
mysql_data:
注意最后那个顶层的 volumes: 声明,这是在告诉 Docker:" 帮我创建一个叫 mysql_data 的命名卷。" 如果你忘了写这个声明,Docker Compose 会报错。
3.2 Bind Mounts 用法
services:
web:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html
这里 ./nginx.conf 是宿主机上的相对路径,:ro 表示只读挂载——容器只能读这个文件,不能改。
3.3 混合使用
实际项目中,两种方式经常一起用:
services:
app:
image: node:20-alpine
volumes:
- ./src:/app/src # Bind Mount: 开发时代码热更新
- node_modules:/app/node_modules # Named Volume: 依赖包持久化
- app_logs:/app/logs # Named Volume: 日志持久化
volumes:
node_modules:
app_logs:
这个组合很经典:源码用 Bind Mount 方便开发调试,而 node_modules 用 Named Volume 避免宿主机和容器的依赖冲突。
四、MySQL 数据持久化实操
说了这么多理论,我们来动手。这是大家最常遇到的场景——MySQL 数据持久化。
4.1 创建项目
mkdir -p ~/docker-mysql-demo && cd ~/docker-mysql-demo
创建 docker-compose.yml:
services:
mysql:
image: mysql:8.0
container_name: demo-mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass123
MYSQL_DATABASE: demo_db
MYSQL_USER: demo_user
MYSQL_PASSWORD: demopass123
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
ports:
- "3307:3306"
restart: unless-stopped
volumes:
mysql_data:
name: demo-mysql-data
创建初始化脚本 init.sql:
USE demo_db;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name, email) VALUES
('张三', 'zhangsan@example.com'),
('李四', 'lisi@example.com'),
('王五', 'wangwu@example.com');
4.2 启动并验证
# 启动服务
docker compose up -d
# 预期输出:
# [+] Running 2/2
# ✔ Volume "demo-mysql-data" Created
# ✔ Container demo-mysql Started
等待 MySQL 完全启动(大约 10-15 秒),然后验证数据:
# 连接数据库查询数据
docker exec demo-mysql mysql -udemo_user -pdemopass123 demo_db \
-e "SELECT * FROM users;"
# 预期输出:
# +----+--------+----------------------+---------------------+
# | id | name | email | created_at |
# +----+--------+----------------------+---------------------+
# | 1 | 张三 | zhangsan@example.com | 2026-03-06 10:00:00 |
# | 2 | 李四 | lisi@example.com | 2026-03-06 10:00:00 |
# | 3 | 王五 | wangwu@example.com | 2026-03-06 10:00:00 |
# +----+--------+----------------------+---------------------+
4.3 验证持久化
关键时刻到了——删掉容器,数据还在不在?
# 插入一条新数据
docker exec demo-mysql mysql -udemo_user -pdemopass123 demo_db \
-e "INSERT INTO users (name, email) VALUES (' 赵六 ','zhaoliu@example.com');"
# 停止并删除容器(注意:不要加 -v 参数!)docker compose down
# 重新启动
docker compose up -d
# 等待 MySQL 启动后查询
docker exec demo-mysql mysql -udemo_user -pdemopass123 demo_db \
-e "SELECT * FROM users;"
# 预期输出: 4 条数据都在,包括后来插入的 "赵六"
数据完好无损。这就是 Named Volume 的威力。
注意一个大坑:docker compose down -v 会把 volumes 一起删掉。生产环境千万别手滑加 -v,这比 rm -rf / 还让人心痛。
五、数据卷的备份与恢复
数据持久化只是第一步。如果磁盘坏了呢?如果要迁移到另一台服务器呢?备份才是真正的保险。
5.1 备份
# 创建备份目录
mkdir -p ~/backups
# 方法一:使用 mysqldump(推荐,跨版本兼容性好)docker exec demo-mysql mysqldump -udemo_user -pdemopass123 demo_db \
> ~/backups/demo_db_backup.sql
# 方法二:直接备份数据卷(适合整体迁移)docker run --rm \
-v demo-mysql-data:/source:ro \
-v ~/backups:/backup \
alpine tar czf /backup/mysql_volume_backup.tar.gz -C /source .
# 预期输出(方法二):
#(无输出表示成功,可以 ls 验证)ls -lh ~/backups/
# -rw-r--r-- 1 root root 1.2K ... demo_db_backup.sql
# -rw-r--r-- 1 root root 5.8M ... mysql_volume_backup.tar.gz
方法二的原理:启动一个临时的 Alpine 容器,把数据卷挂载为只读,然后打包压缩到备份目录。用完容器自动销毁(--rm),干净利落。
5.2 恢复
# 方法一:从 SQL 文件恢复
docker exec -i demo-mysql mysql -udemo_user -pdemopass123 demo_db \
< ~/backups/demo_db_backup.sql
# 方法二:从卷备份恢复
docker compose down
docker volume rm demo-mysql-data
docker volume create demo-mysql-data
docker run --rm \
-v demo-mysql-data:/target \
-v ~/backups:/backup:ro \
alpine tar xzf /backup/mysql_volume_backup.tar.gz -C /target
docker compose up -d
5.3 定时备份脚本
生产环境建议用 crontab 定时备份:
#!/bin/bash
# backup-mysql.sh
BACKUP_DIR=~/backups/mysql
DATE=$(date +%Y%m%d_%H%M%S)
KEEP_DAYS=7
mkdir -p "$BACKUP_DIR"
docker exec demo-mysql mysqldump -udemo_user -pdemopass123 \
--all-databases --single-transaction \
> "$BACKUP_DIR/all_db_$DATE.sql"
# 清理过期备份
find "$BACKUP_DIR" -name "*.sql" -mtime +$KEEP_DAYS -delete
echo "Backup completed: all_db_$DATE.sql"
# 添加定时任务:每天凌晨 3 点执行
crontab -e
# 添加这一行:# 0 3 * * * /path/to/backup-mysql.sh >> /var/log/mysql-backup.log 2>&1
六、数据卷管理常用命令
日常运维中,这几个命令用得最多:
# 查看所有数据卷
docker volume ls
# 查看数据卷详情(存储路径、创建时间等)docker volume inspect demo-mysql-data
# 删除未使用的数据卷(危险操作,先确认!)docker volume prune
# 删除指定数据卷
docker volume rm demo-mysql-data
docker volume prune 会删掉所有没被容器引用的卷。听起来很智能,但如果你刚好 docker compose down 了一个项目,它的卷就变成 " 未使用 " 了。所以执行前一定要三思。
七、常见问题 Q&A
Q1: Named Volume 的数据到底存在宿主机哪里?
Linux 上默认在 /var/lib/docker/volumes/。你可以用 docker volume inspect 查看 Mountpoint 字段。但不建议直接操作这个目录,通过 Docker 命令管理更安全。
Q2: docker compose down 和 docker compose down -v 有什么区别?
down 只删除容器和网络,数据卷会保留。down -v 会把声明的 Named Volumes 也一起删掉。生产环境请务必确认你的肌肉记忆里没有 -v。我见过不止一个团队因为这个参数翻车。
Q3: Bind Mount 的权限问题怎么解决?
这是 Linux 上最常见的坑。容器内进程的 UID/GID 和宿主机不一致,会导致 "Permission denied"。解决方案有三个:
- 在 Dockerfile 中用
RUN chown设置正确权限 - 在 Compose 中用
user: "1000:1000"指定运行用户 - 对于 MySQL 等官方镜像,它们通常会自动处理权限,不需要额外配置
八、小结
今天我们学了 Docker 数据持久化的核心知识:
- Named Volumes 适合生产环境,Docker 帮你管理,省心省力
- Bind Mounts 适合开发环境,代码热更新、配置文件挂载很方便
- MySQL 数据持久化的关键是把
/var/lib/mysql挂载到 Named Volume - 持久化不等于安全,定期备份 才是数据的最后防线
docker compose down -v是个危险命令,生产环境慎用
数据卷解决了 " 数据往哪放 " 的问题。但真实项目不只有数据库,还有前端、后端、缓存、消息队列……它们之间怎么协作?
明天 Day 18 多容器全栈应用实战,我们会用 Compose 编排一个完整的前后端 + 数据库 + Redis 的全栈应用,把前几天学的网络、数据卷、环境变量全部串起来。这才是 Compose 真正发力的地方,敬请期待。