# Docker 資料卷自動備份到 GitHub：適合 1Panel 的低成本容災方案

> Canonical: https://vxi.me/zh-tw/posts/docker-volume-backup-github-sync/
> Locale: zh-tw
> Published: 2026-03-25
> Updated: 2026-03-25
> Markdown: https://vxi.me/zh-tw/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 伺服器真正擁有一套穩定可用的自動備份”。
