# Docker 数据卷自动备份到 GitHub：适合 1Panel 的低成本容灾方案

> Canonical: https://vxi.me/posts/docker-volume-backup-github-sync/
> Locale: zh-cn
> Published: 2026-03-25
> Updated: 2026-03-25
> Markdown: https://vxi.me/posts/docker-volume-backup-github-sync.md

自己跑 Docker 服务，平时最容易忽略的一件事，就是你以为“容器都在正常运行”，所以备份这件事可以晚点再说。真正出问题时，麻烦往往不是把容器重新拉起来，而是卷里的数据、面板里的配置、还有你自己改过的那些东西，到底还能不能找回来。

我最后定了一套比较克制的方案，不追求很重的备份系统，也不先上对象存储。思路很简单：每天把 Docker 卷和 1Panel 配置打包一份，本地只留最近 7 天，再把压缩包同步到 GitHub 私有仓库。这样做不花哨，但它至少把“最近几天能快速恢复”和“远端还有一份兜底”这两件事先解决了。

如果你现在正好在搜这些关键词，比如 Docker 自动备份、Docker 卷备份到 GitHub、1Panel 备份方案，这篇文章基本就是围绕这几个问题展开的。

## 我为什么没把它做成一套很重的备份系统

很多人一提备份，第一反应就是对象存储、版本归档、异地多副本、专门备份平台。方向当然没问题，但对个人服务器或者小团队机器来说，真正卡住的往往不是“方案不够先进”，而是根本没有一套能稳定每天跑的东西。

我更在意的是三件事：

- 先自动跑起来，不靠人记着手工执行
- 出问题时最近几天能快速恢复
- 备份文件别只放在当前这台服务器上

所以这次没有把链路做得特别复杂，而是压成了下面这个结构：

```text
Docker 卷 / 1Panel 配置
        ↓
  docker-sync 脚本
        ↓
本地生成 backup_<timestamp>
        ↓
删除 7 天前旧备份
        ↓
复制压缩包到 Git 仓库目录
        ↓
Git LFS 跟踪 .tar.gz
        ↓
推送到 GitHub 私有仓库
```

它不适合特别大的备份量，也不是严格意义上的企业级容灾。但如果你现在是用 1Panel 跑几个站、几个数据库、几个服务，这种“7 天本地 + GitHub 私有仓库同步”的组合其实挺实用。

## 先把 GitHub 信任打通，不然后面都白搭

这件事我建议先做，而且一定要先手工验证一次。原因很简单，后面无论你用 Shell、1Panel 计划任务还是别的自动化方式，最终都得落到“这台服务器能不能把东西推到 GitHub”。

在服务器上先执行：

```bash
ssh-keygen -t ed25519 -C "your-email@example.com"
cat ~/.ssh/id_ed25519.pub
ssh -T git@github.com
```

第一条是生成密钥，第二条把公钥拷出来放进 GitHub 的 SSH keys，第三条是第一次手工验证连接。

这一步别偷懒。很多自动任务一开始就失败，不是脚本有问题，而是 SSH 压根没通。你把这件事提前确认掉，后面排查会省很多时间。

顺手说一下，备份仓库最好单独建，不要和业务代码混在一起。我这里用的是这种形式：

```text
git@github.com:kuangyongjie/my-docker-backup.git
```

仓库最好设成 `Private`，这个没什么好犹豫的。

## 我把整个流程压成了一个 `docker-sync` 命令

如果备份逻辑只是临时放在某个脚本目录里，过两个月你大概率就忘了它到底在哪、该怎么跑、改了什么。我的习惯是，能长期维护的东西，入口最好固定。

所以我最后直接把它封装成了一个系统命令：

```text
/usr/local/bin/docker-sync
```

这样无论是我自己手工执行，还是后面让 1Panel 调度，调用方式都一样。下面这段脚本就是核心：

```bash
cat << 'EOF' > /usr/local/bin/docker-sync
#!/bin/bash

GITHUB_REPO="git@github.com:kuangyongjie/my-docker-backup.git"
GIT_USER_NAME="kuangyongjie"
GIT_USER_EMAIL="admin@local.host"
LOCAL_SYNC_PATH="/root/github_sync_space"

if ! command -v git-lfs &> /dev/null; then
    apt-get update && apt-get install -y git git-lfs && git lfs install
fi

if [ ! -d "$LOCAL_SYNC_PATH/.git" ]; then
    echo "--- 第一次运行：初始化同步目录 ---"
    rm -rf "$LOCAL_SYNC_PATH"
    GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git clone "$GITHUB_REPO" "$LOCAL_SYNC_PATH"
    cd "$LOCAL_SYNC_PATH"
    git lfs track "*.tar.gz" && git add .gitattributes
    git config user.name "$GIT_USER_NAME" && git config user.email "$GIT_USER_EMAIL"
    git commit -m "Init LFS"
else
    cd "$LOCAL_SYNC_PATH" && git pull origin main --rebase
fi

TAG=$(date +%Y%m%d_%H%M%S)
DIR="/root/docker_backups/backup_$TAG"
mkdir -p "$DIR"

echo "--- 正在抓取本地数据 ---"
for vol in $(docker volume ls -q); do
    tar -zcf "$DIR/vol_$vol.tar.gz" -C $(docker volume inspect $vol --format '{{.Mountpoint}}') .
done

tar -zcf "$DIR/1panel_apps_config.tar.gz" /opt/1panel/apps

echo "--- 正在清理本地 7 天前的旧备份 ---"
find /root/docker_backups/ -maxdepth 1 -name "backup_*" -type d -mtime +7 -exec rm -rf {} +

echo "--- 正在同步到 GitHub ---"
cp "$DIR"/*.tar.gz "$LOCAL_SYNC_PATH/"
cd "$LOCAL_SYNC_PATH"
git add .
git commit -m "Auto Backup $TAG"
git push origin main
echo "--- 完美！所有任务已完成 ---"
EOF

chmod +x /usr/local/bin/docker-sync
```

## 这段脚本其实没那么复杂，拆开看就六件事

很多人一看到长一点的 Shell 脚本就不想看了，但这段逻辑其实挺直白。

它无非就是：

1. 如果没装 `git-lfs`，先装
2. 第一次运行时，克隆备份仓库并初始化 LFS
3. 遍历 Docker 卷，把每个卷单独打成 `tar.gz`
4. 顺手把 `/opt/1panel/apps` 也打进去
5. 清理 7 天前的本地备份目录
6. 把压缩包复制到同步目录，提交并推到 GitHub

这里我比较喜欢的一点是，链路没有额外引入很重的依赖。Shell、Git、Git LFS、Docker 卷路径，这些东西本来就在机器上或者很容易装。后面哪一步坏了，也比较容易定位。

真正需要注意的反而不是“脚本长不长”，而是两个细节：

- `git commit` 在没有新文件变更时会直接失败
- `docker volume ls -q` 会把所有卷都扫进去，所以你最好知道自己这台机器上到底有哪些卷

如果你后面要继续打磨，可以先从这两个点入手。

## 1Panel 这边别塞长脚本，计划任务只负责调用

我不太建议把一大段备份脚本直接贴进 1Panel 计划任务里。短期看似乎省事，长期维护会很烦，尤其是你以后要改仓库地址、保留天数、用户信息的时候。

更省心的方式是：

1. 先在命令行里把 `docker-sync` 手工跑通
2. 再进 1Panel 创建计划任务
3. 任务类型选 Shell 脚本
4. 每天跑一次，比如凌晨 `03:00`
5. 脚本内容只写：

```bash
docker-sync
```

这样后面如果某天备份失败，你只需要 SSH 上去手工跑一次 `docker-sync`，马上就能看日志，不用先回面板里翻长脚本。

## 真要恢复的时候，流程其实比备份更重要

很多备份方案写到最后，只停在“文件已经存起来了”。但实际出事时，你最关心的是下一步怎么恢复，而不是备份当初多优雅。

如果服务器挂了，恢复大致就是这条线：

1. 在新服务器克隆备份仓库
2. 找到你要的那个时间点压缩包
3. 解压对应的 `vol_xxx.tar.gz`
4. 用 `docker volume create` 重新创建目标卷
5. 把数据拷回这个卷的 `Mountpoint`
6. 重新创建容器并挂载这个卷

1Panel 相关配置也是类似思路，先把配置目录拿回来，再重新挂到新的运行环境里。

这里真正麻烦的不是“会不会解压”，而是你平时有没有把卷、服务、用途对应清楚。否则到了恢复现场，文件在，但你不知道哪个卷对应哪个服务，时间照样会被拖很久。

## 我自己会常看的几个命令

平时维护时，我比较常用的其实就这几个：

| 需求 | 命令 |
| --- | --- |
| 手动立即备份 | `docker-sync` |
| 看本地备份占了多少空间 | `du -sh /root/docker_backups` |
| 看本地备份目录 | `ls -lh /root/docker_backups` |
| 看最近同步记录 | `cd /root/github_sync_space && git log -n 5` |
| 调整本地保留天数 | `nano /usr/local/bin/docker-sync` |

如果你只是想把 7 天改成 14 天，或者改成 3 天，直接改 `find ... -mtime +7` 那一段就行，不需要重构整个脚本。

## 这套方案最容易翻车的地方

先说结论，通常不是压缩命令本身有多难，而是下面这些地方最容易出问题：

- 服务器到 GitHub 的 SSH 信任没配完整
- `git-lfs` 没装或者没初始化
- 本地同步目录第一次初始化时权限不对
- 计划任务能跑，但仓库里其实没有新文件，所以 `git commit` 失败

还有一个现实问题也得提前接受：GitHub 私有仓库可以拿来做这种 Docker 备份同步，但它更适合配置、小型数据库卷、常见应用数据这类体量还算可控的场景。真要是备份越来越大，后面迟早还是要往对象存储或者专门的备份介质迁。

所以这套方案的定位我觉得要看准一点，它不是“最终版容灾体系”，而是“先让你的 1Panel / Docker 服务器真正拥有一套稳定可用的自动备份”。
