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

5 阅读2分钟

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

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

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

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

想做到: 定时备份 Volume 数据

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

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

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

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

为什么不推荐:

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

权限、属主可能丢失

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

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

#!/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:只读挂载,防止备份过程中意外修改数据。

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

tar -tzf "$OUT" | head -20

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

$ tar -tzf volume_uptime-kuma_20261013_030000.tar.gz | head
./
./kuma.db
./kuma.db-shm
./kuma.db-wal
./upload/
./screenshots/

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

# 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 直接打包——文件可能在磁盘上不一致。

正确做法:

# 先用 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(物理)快、省空间必须同版本、不能部分恢复

两者互补,都要有。

验证效果 模拟灾难

# 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 目录

# 错误
sudo cp -r /var/lib/docker/volumes/uptime-kuma/_data /backup/

后果:

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

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

恢复后容器起不来

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

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

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

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

数据库文件冲突,SQLite 报错

配置合并错乱

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

alpine sh -c "rm -rf /data/* /data/..?* /data/.[!.]*"

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

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

出事那天:

$ tar -tzf backup.tar.gz
tar: This does not look like a tar archive

或者:

$ tar -tzf backup.tar.gz
./

空的。

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

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

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

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