Docker 数据备份与恢复实战
容器可以随时重建,数据不能。数据库文件、应用状态、用户上传的内容,都躺在卷和挂载目录里。这篇文章给出一套完整的备份方案:备份什么、怎么备、怎么定时、最关键的是——怎么确认备份真的能恢复。
第一条原则:不能恢复的备份等于没有备份。先想清楚「丢了数据要几天才能找回来」,再决定备份频率和保留策略。
一、先盘点:要备份什么
打开 compose 文件,把所有 volumes 和挂载目录列出来,按类型分成三类:
# docker-compose.yml 里的数据载体
services:
mysql:
volumes:
- mysql-data:/var/lib/mysql # ① 命名卷:数据库文件
app:
volumes:
- ./uploads:/app/uploads # ② 绑定挂载:用户上传
- ./config:/app/config:ro # (只读配置不用备,git 里有)
redis:
volumes:
- redis-data:/data # ① 命名卷:缓存(可不备或低频)
volumes:
mysql-data:
redis-data:
- 必须高频备份:数据库、有状态应用的数据目录、用户生成内容;
- 不用备份:镜像、容器本身、只读配置文件(应有 git 管理)、可重建的缓存;
- 要留存一份:docker-compose.yml、.env、Dockerfile——重建环境全靠它们。
二、命名卷备份:一条 docker run 打包
备份命名卷不需要停容器。挂一个临时 busybox 容器,把卷挂进去、把备份目录挂进去,tar 打包:
# 卷名先查清楚:compose 项目会加前缀
docker volume ls
# 备份 mysql-data 卷(项目前缀如 myapp_mysql-data)
docker run --rm \
-v myapp_mysql-data:/data:ro \
-v /backup:/backup \
busybox tar czf /backup/mysql-data-$(date +%Y%m%d-%H%M).tar.gz -C /data .
# 查看结果
ls -lh /backup/
参数拆解::ro 只读挂载卷防止备份过程干扰写;-C /data . 打包的是卷内容本身而不是外层目录,恢复时直接解到卷根目录。
备份所有卷:循环处理
for v in $(docker volume ls -q | grep '^myapp_'); do
docker run --rm -v "$v":/data:ro -v /backup:/backup \
busybox tar czf "/backup/$v-$(date +%Y%m%d).tar.gz" -C /data .
done
三、数据库:用数据库自己的工具导出
数据库文件直接 tar 也能备,但生产上强烈建议用官方导出工具——导出的是逻辑数据,跨版本恢复、按表恢复都更灵活,也避免热备时文件不一致。
# MySQL / MariaDB
docker exec myapp-db mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" \
--single-transaction --routines --triggers myapp \
> /backup/myapp-$(date +%Y%m%d).sql
# PostgreSQL
docker exec myapp-pg pg_dump -U myapp myapp \
> /backup/myapp-$(date +%Y%m%d).sql
# 压缩
gzip /backup/myapp-$(date +%Y%m%d).sql
--single-transaction 对 InnoDB 很重要:在单个事务里做一致快照,备份过程中业务正常写入,不会出现备份到一半的不一致状态。SQLite 这类单文件数据库直接
cp 就行,但最好用 sqlite3 db .backup 或 VACUUM INTO,避免热拷贝时文件锁导致备份损坏。四、定时备份 + 保留策略
手动备份坚持不了三天,交给 cron。每天凌晨 3 点执行备份脚本,保留最近 7 份:
# /etc/cron.d/docker-backup
# 分 时 日 月 周 用户 命令
0 3 * * * root /opt/scripts/backup.sh
#!/usr/bin/env bash
# /opt/scripts/backup.sh —— 全量备份脚本
set -euo pipefail
BACKUP_DIR=/backup
DATE=$(date +%Y%m%d)
# 1. 数据库逻辑备份
docker exec myapp-db mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD}" \
--single-transaction --routines myapp | gzip \
> "$BACKUP_DIR/db-$DATE.sql.gz"
# 2. 命名卷打包
for v in mysql-data uploads-data; do
docker run --rm -v "myapp_$v":/data:ro -v "$BACKUP_DIR":/backup \
busybox tar czf "/backup/$v-$DATE.tar.gz" -C /data .
done
# 3. 只保留最近 7 份,更早的删掉
find "$BACKUP_DIR" -name '*.gz' -mtime +7 -delete
# 4. 简单校验:包非空且 gzip 完整性
for f in "$BACKUP_DIR"/*"$DATE"*; do
gzip -t "$f" || echo "⚠ 备份损坏: $f"
done
chmod +x /opt/scripts/backup.sh
# 手动跑一次验证
/opt/scripts/backup.sh
备份别和容器数据放同一块磁盘——机器挂了盘一起没。有条件的 rsync 或 rclone 再同步一份到异地/对象存储,这是"最后一道保险"的最后一道。
五、恢复:卷还原
恢复步骤和备份是对称的:临时容器挂新卷,把包解进去,再启动容器。以 mysql-data 为例:
# 1. 停掉依赖该卷的容器
docker compose stop mysql
# 2. 用备份内容填充新卷
docker run --rm \
-v myapp_mysql-data:/data \
-v /backup:/backup \
busybox tar xzf /backup/mysql-data-20260803.tar.gz -C /data
# 3. 启动
docker compose start mysql
数据库逻辑备份的恢复:
gunzip -c /backup/db-20260803.sql.gz | \
docker exec -i myapp-db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" myapp
如果卷已经损坏要彻底重建:docker compose down -v 删掉旧卷(数据没了,只剩备份),重新 docker compose up -d 让容器建新卷,再按上面步骤灌入备份。
六、最重要的一步:恢复演练
备份脚本跑了一年,从没恢复过——这不叫有备份。每月抽一天,在测试环境(或本机)完整走一遍恢复流程:
# 演练脚本:恢复备份 → 启动 → 检查数据
docker compose down -v # 清空(测试环境)
docker compose up -d db
docker run --rm -v myapp_mysql-data:/data -v /backup:/backup \
busybox tar xzf /backup/mysql-data-最新.tar.gz -C /data
docker compose up -d
# 然后查一条业务数据,确认是最新时间点的数据
- 记录每次演练的耗时,作为 RTO(恢复时间目标)——老板问"多久能恢复",答案就在这里;
- 演练能暴露真实问题:备份文件损坏、卷名记错、密码过期、磁盘空间不足;
- 演练不是可选步骤,是备份方案的一部分。
七、方案速查
备份对象 方法 频率 保留
------------- ---------------------------- -------- ------
MySQL/PG mysqldump / pg_dump 每天 3 点 7 天
命名卷 busybox tar 每天 3 点 7 天
上传文件/数据 rsync / tar 每天 3 点 7 天
compose/配置 git 仓库 每次改动 永久
异地副本 rclone 同步到对象存储 每天 30 天
总结:备份方案 = 盘点数据 + 卷打包/数据库导出 + cron 定时 + 保留策略 + 异地副本 + 定期恢复演练。前四步半天能搭完,演练才是长期要养成的习惯。
延伸阅读:Docker Compose 网络与数据卷管理 · 六服务 Docker Compose 编排实战 · Ubuntu 24.04 服务器初始化与 Docker 部署实战
← 返回首页
延伸阅读:Docker Compose 网络与数据卷管理 · 六服务 Docker Compose 编排实战 · Ubuntu 24.04 服务器初始化与 Docker 部署实战