wuee
Docker · 运维 / 2026.08

Docker 数据备份与恢复实战

Docker 卷备份 · 数据库导出 · cron 定时 · 恢复演练

容器可以随时重建,数据不能。数据库文件、应用状态、用户上传的内容,都躺在卷和挂载目录里。这篇文章给出一套完整的备份方案:备份什么、怎么备、怎么定时、最关键的是——怎么确认备份真的能恢复。

第一条原则:不能恢复的备份等于没有备份。先想清楚「丢了数据要几天才能找回来」,再决定备份频率和保留策略。

一、先盘点:要备份什么

打开 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:

二、命名卷备份:一条 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
# 然后查一条业务数据,确认是最新时间点的数据

七、方案速查

备份对象       方法                          频率      保留
-------------  ----------------------------  --------  ------
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 部署实战
← 返回首页