Docker Volume 备份与恢复实战:不停机迁移数据

背景 服务器上几个服务的数据都存在 Docker Volume 里:

uptime-kuma → Kuma 的所有监控配置、历史记录

mariadb-data → 数据库所有表和数据

容器本身随时可以删了重建,但Volume 里的数据丢了就真丢了。

想做到: 定时备份 Volume 数据

不停机(不能为了备份把 Kuma 停了)

能恢复(备份出来要验证真能还原)

原因 Docker Volume 默认存在 /var/lib/docker/volumes/,但你不能直接 cp:

bash 复制代码
原因
Docker Volume 默认存在 /var/lib/docker/volumes/,但你不能直接 cp:

为什么不推荐:

容器正在写文件时,拷出来的数据可能是半截的

权限、属主可能丢失

MariaDB 这种数据库,直接拷文件大概率文件损坏

正确做法:用临时容器挂载 volume,在容器里打包。 解决 一、备份:临时容器打包

bash 复制代码
#!/usr/bin/env bash
set -uo pipefail

VOL="uptime-kuma"
BACKUP_DIR="/home/user/backups"
DATE=$(date +%Y%m%d_%H%M%S)
OUT="$BACKUP_DIR/volume_${VOL}_${DATE}.tar.gz"

mkdir -p "$BACKUP_DIR"

docker run --rm \
  -v "$VOL":/data:ro \
  -v "$BACKUP_DIR":/out \
  alpine tar czf "/out/$(basename "$OUT")" -C /data .

echo "备份完成: $OUT"

关键点:

参数 作用
-v "$VOL":/data:ro 把 volume 挂进来,只读
-v "$BACKUP_DIR":/out 把备份目录挂进来
alpine 极小的镜像,几 MB
tar czf 打包压缩

为什么加 :ro:只读挂载,防止备份过程中意外修改数据。

二、验证备份内容 打包完立刻验证,别等需要恢复时才发现包是空的。

bash 复制代码
tar -tzf "$OUT" | head -20

期望:能看到 Kuma 的数据库文件、配置文件等。

bash 复制代码
$ tar -tzf volume_uptime-kuma_20261013_030000.tar.gz | head
./
./kuma.db
./kuma.db-shm
./kuma.db-wal
./upload/
./screenshots/

如果只看到 ./ 一行,说明打包时 volume 是空的(挂载点写错或容器没起过)。 三、恢复:临时容器解包 假设 uptime-kuma 容器挂了,要恢复数据:

bash 复制代码
# 1. 停掉正在跑的容器
docker stop uptime-kuma

# 2. 备份当前 volume(万一恢复失败还能回滚)
docker run --rm \
  -v uptime-kuma:/data \
  -v /home/user/backups:/out \
  alpine tar czf /out/volume_uptime-kuma_before_restore_$(date +%s).tar.gz -C /data .

# 3. 清空 volume
docker run --rm \
  -v uptime-kuma:/data \
  alpine sh -c "rm -rf /data/* /data/..?* /data/.[!.]*"

# 4. 解包恢复
docker run --rm \
  -v uptime-kuma:/data \
  -v /home/user/backups:/backup:ro \
  alpine sh -c "tar xzf /backup/volume_uptime-kuma_20261013_030000.tar.gz -C /data"

# 5. 起容器
docker start uptime-kuma

四、MariaDB 的 volume 备份 数据库不能用 tar 直接打包------文件可能在磁盘上不一致。

正确做法:

bash 复制代码
# 先用 mariadb-dump 导出逻辑备份
docker exec my-mariadb mariadb-dump -uroot -p"$DB_PASS" test_db > db.sql

# 再 tar 打包(把 dump 文件和 volume 一起)
docker run --rm \
  -v mariadb-data:/data:ro \
  -v /home/user/backups:/out \
  alpine sh -c "tar czf /out/mariadb_volume_$(date +%s).tar.gz -C /data ."

# 逻辑备份单独存一份
gzip db.sql
mv db.sql.gz /home/user/backups/

为什么两种都做:

备份类型 优点 缺点
mariadb-dump(逻辑) 可读、跨版本、可以只恢复一张表 慢、占空间大
tar volume(物理) 快、省空间 必须同版本、不能部分恢复

两者互补,都要有。

验证效果 模拟灾难

sql 复制代码
# 1. 记录当前 Kuma 里的监控数量
docker exec uptime-kuma sqlite3 /app/data/kuma.db "SELECT COUNT(*) FROM monitor;"
# 输出:18

# 2. 删掉 volume(模拟灾难)
docker stop uptime-kuma
docker rm uptime-kuma
docker volume rm uptime-kuma

# 3. 重新创建容器
cd /home/user/uptime-kuma
docker compose up -d

# 4. 此时监控数量应该是 0(空数据库)
docker exec uptime-kuma sqlite3 /app/data/kuma.db "SELECT COUNT(*) FROM monitor;"
# 输出:0

# 5. 恢复备份
docker stop uptime-kuma
docker run --rm -v uptime-kuma:/data -v /home/user/backups:/backup:ro \
  alpine sh -c "tar xzf /backup/volume_uptime-kuma_20261013_030000.tar.gz -C /data"
docker start uptime-kuma

# 6. 验证
docker exec uptime-kuma sqlite3 /app/data/kuma.db "SELECT COUNT(*) FROM monitor;"
# 输出:N ✅

从 0 到 18,完整恢复。

三个坑 坑 1:直接 cp volume 目录

bash 复制代码
# 错误
sudo cp -r /var/lib/docker/volumes/uptime-kuma/_data /backup/

后果:

SQLite/MariaDB 文件可能损坏(复制到一半被写)

属主、权限、SELinux 上下文可能丢失

恢复后容器起不来

正确:用临时容器挂载 + tar。

坑 2:恢复时忘了清空原 volume

bash 复制代码
# 错误:直接解包到已有数据的 volume
docker run --rm -v uptime-kuma:/data -v /backup:/backup:ro \
  alpine sh -c "tar xzf /backup/xxx.tar.gz -C /data"

后果:新数据和旧数据混在一起,可能出现:

数据库文件冲突,SQLite 报错

配置合并错乱

正确:解包前先清空。清空命令要小心,用:

arduino 复制代码
alpine sh -c "rm -rf /data/* /data/..?* /data/.[!.]*"

这行的含义:删掉所有文件,包括 . 开头的隐藏文件。

坑 3:备份完不验证 常见:备份脚本写好了,跑了一年,从没检查过包里有什么。

出事那天:

bash 复制代码
$ tar -tzf backup.tar.gz
tar: This does not look like a tar archive

或者:

bash 复制代码
$ tar -tzf backup.tar.gz
./

空的。

正确做法:每次备份后至少验证两件事:

包能解压:tar -tzf xxx.tar.gz > /dev/null

包非空:tar -tzf xxx.tar.gz | wc -l > 1

进阶:定期做恢复演练(本文第四部分),别等真出事。

相关推荐
数据库小学妹1 小时前
向量检索最小可运行实现:HNSW索引参数与召回率/延迟权衡
数据库·后端
suhan1 小时前
【数据结构】三、栈和队列:5.顺序队列(循环队列)(初始化,判空判满,入队,出队,实例)
数据结构·后端·算法
看浪的路人1 小时前
第7讲:大模型后端服务化——Go 实现高并发 LLM Proxy / Router / Fallback
开发语言·后端·golang
suhan1 小时前
【数据结构】三、栈和队列:3.链栈(链栈栈的初始化,判空,进栈,出栈,读取栈顶,链栈实例)
数据结构·后端·算法
邱杰4701 小时前
数智人事系统(十一):Spring Boot + MyBatis-Plus 的 CRUD 落地
后端·github
大雄很用心1 小时前
如何理解时间复杂度与空间复杂度?| 面试刷题笔记02
算法
姜鱼问生1 小时前
服务器磁盘满了?从告警到清理的完整排查
后端·算法·架构
邱杰4701 小时前
数智人事系统(十三):考勤打卡与出勤日历
后端·github
suhan1 小时前
【数据结构】三、栈和队列:4.栈的应用(括号匹配,四则运算表达式求值,进制转换,递归)
数据结构·后端·算法