背景 服务器上几个服务的数据都存在 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
进阶:定期做恢复演练(本文第四部分),别等真出事。