1. 为什么Docker Compose重启会丢数据?
"昨天还好好的服务,今天重启后数据库全空了!"——这是许多开发者初次使用Docker Compose时踩过的坑。问题的核心在于:默认情况下,Docker容器是临时(Ephemeral)的。当容器被销毁(比如执行docker-compose down
)时,容器内部生成的文件会随容器生命周期结束而消失。这种特性在无状态服务中没问题,但对数据库、文件存储等场景就是灾难。
典型翻车现场:
- 开发环境用
docker-compose up
启动MySQL后存入测试数据 - 修改配置后执行
docker-compose down && docker-compose up -d
- 发现数据库变成初始状态,测试数据全丢
- 开发者陷入"我数据呢?"的哲学思考
2. 数据持久化的三种武器
2.1 方案一:用Docker卷(Volume)当"U盘"
技术栈:Docker Compose + Volume
原理:将容器内关键目录映射到宿主机的持久化存储空间
version: '3.8'
services:
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql # 将数据库文件存放到命名卷
environment:
MYSQL_ROOT_PASSWORD: secret
volumes:
mysql_data: # 声明一个持久化卷
验证操作:
# 启动服务
docker-compose up -d
# 进入容器插入测试数据
docker exec -it [容器ID] mysql -uroot -psecret -e "CREATE DATABASE demo;"
# 销毁容器
docker-compose down
# 重新启动
docker-compose up -d
# 检查数据库是否存在(应保留)
docker exec -it [新容器ID] mysql -uroot -psecret -e "SHOW DATABASES;"
优点:
- 数据独立于容器生命周期
- 跨主机迁移方便(卷可导出)
- 性能接近原生磁盘
缺点:
- 需要手动清理废弃卷
- 不直观查看卷内文件(需通过
docker volume inspect
)
2.2 方案二:绑定宿主机目录(Bind Mount)
技术栈:Docker Compose + 宿主机目录
适用场景:需要直接修改配置文件的开发环境
services:
redis:
image: redis:6
volumes:
- ./redis_data:/data # 映射到当前目录的redis_data文件夹
文件结构:
project/
├── docker-compose.yml
└── redis_data/ # 自动创建的空目录
验证方法:
# 启动后执行数据写入
docker exec [容器ID] redis-cli SET foo "bar"
# 查看宿主机目录
ls ./redis_data
# 应看到dump.rdb文件
# 重启服务后检查数据
docker exec [新容器ID] redis-cli GET foo # 应返回"bar"
优点:
- 开发调试直观(直接修改宿主机文件)
- 无需管理卷生命周期
缺点:
- 路径依赖导致部署不够灵活
- 可能引发权限问题(容器用户与宿主机用户UID不一致)
2.3 方案三:临时卷(Tmpfs)的特殊玩法
技术栈:Docker Compose + Tmpfs
适用场景:敏感数据内存暂存
services:
temp_service:
image: alpine
tmpfs:
- /app/temp_cache # 内存挂载目录
特点:
- 数据仅存于内存,重启后消失
- 适合存放临时会话、缓存等非持久数据
3. 混合模式实战:Web应用+数据库+文件存储
技术栈:Node.js + MySQL + MinIO
场景:一个包含用户上传功能的Web服务
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- ./uploads:/app/uploads # 绑定上传目录
depends_on:
- db
- minio
db:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: supersecret
minio:
image: minio/minio
volumes:
- minio_data:/data
command: server /data
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: admin123
volumes:
mysql_data:
minio_data:
关键点说明:
- Web服务通过绑定挂载实现上传目录持久化
- 数据库使用命名卷保证事务安全
- 对象存储使用独立卷隔离业务数据
4. 避坑指南:那些你一定会遇到的坑
4.1 权限的幽灵
现象:容器报错"Permission Denied"
根因:容器内进程用户(如MySQL用mysql
用户)无宿主机目录写权限
解决方案:
volumes:
- ./data:/var/lib/mysql:z # SELinux上下文标记
# 或
- ./data:/var/lib/mysql:ro # 只读挂载(慎用)
4.2 备份策略不能少
经典错误:以为用了卷就万事大吉
正确姿势:
# 定期备份MySQL卷
docker run --rm -v mysql_data:/source -v $(pwd)/backups:/backup \
alpine tar czf /backup/mysql_$(date +%Y%m%d).tar.gz -C /source .
# MinIO配置版本控制
docker exec minio mc admin bucket set mybucket versioning enable
4.3 不要忽视版本兼容性
血泪案例:
- Compose文件版本影响卷声明方式
- 不同Docker引擎版本对
tmpfs
的支持差异
5. 终极武器:Docker卷的进阶管理
5.1 查看卷使用情况
docker system df -v # 显示卷磁盘占用
5.2 跨主机迁移
# 导出卷
docker run --rm -v mysql_data:/source -v $(pwd):/backup \
alpine tar cf /backup/mysql_data.tar -C /source .
# 新主机导入
docker volume create mysql_data
docker run --rm -v mysql_data:/target -v $(pwd):/backup \
alpine tar xf /backup/mysql_data.tar -C /target
5.3 自动清理策略
# 删除所有未被使用的卷
docker volume prune
# 保留最近3天的备份
find ./backups -name "*.tar.gz" -mtime +3 -exec rm {} \;
6. 总结:数据无价,且用且珍惜
通过合理的卷策略,我们可以让Docker Compose既保持"一键启停"的便捷性,又实现关键数据的持久化。记住三个黄金法则:
- 重要数据必须显式声明存储位置
- 开发环境优先使用绑定挂载,生产环境用命名卷
- 备份方案要和存储方案同时设计