Watchtower 與 Diun,容器自動更新的風險與實測

發表時間:2026 年 11 月 14 日 | 更新日期:2026 年 11 月 14 日 | 編輯:雅寶社區編輯團隊
Watchtower 與 Diun,容器自動更新的風險與實測 - 雅寶社區 · 頂客論壇

Watchtower 的優點是設定極簡。一份 Compose 檔、幾行環境變數就能跑起來,甚至連通知都不設定也能運作。對於「我就是想讓它自動更新」的人來說,門檻低到幾乎沒有。

但它的缺點也來自同一個地方:它太快、太自動、太不打擾你。更新成功時你不會知道,更新失敗時你可能也不會馬上知道(除非你設定通知),而更新造成的資料損壞,往往要好幾天才會浮現。

Diun:只負責通知的守門員

Diun(Docker Image Update Notifier)的定位完全不同。它會監控你指定的映像檔,定期檢查 registry 上是否有新版本,一旦發現更新,就透過各種管道(Telegram、Discord、Slack、Email、Gotify、ntfy、Webhook 等)通知你。

然後呢?沒有然後了。Diun 不會動你的容器,不會幫你 pull,不會幫你重啟。它只告訴你「有新版本了,換你決定了」。

這個設計乍看之下很「不自動」,但對於有狀態服務、對於生產環境、對於任何你不想在半夜被驚醒的場景來說,這才是負責任的做法。它把「發現更新」自動化,把「決定是否更新」留給人。

Diun 的設定比 Watchtower 稍微複雜一點,需要設定通知管道、需要決定監控範圍(可以用 label 精準指定),但整體仍然不算難。真正的成本在於:你必須真的去看通知,並且真的去執行更新。而這正是很多人最後回頭擁抱 Watchtower 的原因——人性。

三、容器自動更新的五大風險剖析

以下這五個風險,是筆者在實際使用與協助他人排查時最常遇到的。它們不是理論上的可能性,而是幾乎每個長期使用 Watchtower 的人都會踩到的坑。

風險一:未經測試的版本直接進入你的「生產環境」

對大多數自架玩家來說,家裡那台 NAS 或小主機上的服務,其實就是「生產環境」。家人用的相簿、密碼管理器、筆記服務,一旦掛掉就是真實的不便。而 Watchtower 的更新流程中,完全沒有「測試」這個環節。

上游推送的 latest 標籤,不代表它經過完整測試。很多專案的 latest 就是 main 分支的最新建置,偶爾會出現「能編譯但跑起來有問題」的版本。更常見的是「功能正常但行為改變」——例如預設值改了、某個環境變數被廢棄、API 端點調整。這些變更在 CI 裡可能測不出來,因為 CI 不會測你的特定使用情境。

Watchtower 不會問你「這個版本你要不要」,它只會比對 digest,不一樣就換。等到你發現服務怪怪的,已經是好幾天後的事,而且你一開始根本不會想到是「自動更新」造成的,因為你「什麼都沒做」。

風險二:資料庫與儲存格式的相容性問題

這是最危險的一類。無狀態服務(例如單純的靜態網站、反向代理)更新失敗,頂多服務中斷,重啟舊版就好。但有狀態服務——資料庫、相簿、筆記、密碼庫——更新失敗可能意味著資料毀損或無法回溯。

許多應用會在啟動時執行資料庫遷移(migration),把 schema 升級到新版本。這個過程通常是單向的:升級到 v5 之後,v4 的程式碼就讀不懂新的 schema。如果你用 Watchtower 自動更新,而新版本有 bug 或遷移失敗,你可能會面臨「新版本跑不起來、舊版本也回不去」的窘境。

更糟的是,有些應用會在遷移過程中修改或刪除舊資料。筆者就見過有人因為 Watchtower 自動把 Immich 更新到某個開發版,導致資料庫遷移後部分中繼資料錯亂,最後只能從備份還原——而備份是三天前的。

Watchtower 有 --monitor-only 模式(只通知不更新),也有 label 可以排除特定容器,但預設行為是「全部更新」。很多人裝了之後根本沒設定排除清單,結果資料庫容器也一起被自動換掉。

風險三:設定檔與環境變數的破壞性變更

容器映像檔更新時,應用程式本身的行為也可能改變。常見的情況包括:

某個環境變數改名或移除,導致新容器啟動時讀不到設定,直接 crash 或使用錯誤的預設值。

設定檔的格式改變(例如從 YAML 改成 TOML,或欄位重新命名),舊設定檔被忽略或解析失敗。

預設的資料目錄、連接埠、使用者 UID 改變,導致權限問題或連線失敗。

這些變更通常會寫在 release notes 裡,但 Watchtower 不會幫你讀 release notes,它只會幫你換映像檔。於是你就會在「服務突然掛掉」的時候,才回頭去查「最近到底更新了什麼」。

而且因為 Watchtower 是「停掉舊容器、建立新容器」,如果新容器啟動失敗,Watchtower 預設並不會自動 rollback 到舊版本。它可能會讓容器處於不断重啟的狀態,或者直接留下一個停止的容器。你的服務就這樣消失了,直到你手動介入。

風險四:映像檔供應鏈安全與 latest 標籤的陷阱

自動更新還有一個比較少被討論的風險:你等於把「執行什麼程式碼」的決定權,交給了上游的推送流程。

如果你用的是 latest 標籤,Watchtower 會無條件接受 registry 上最新的 latest。這意味著:

如果上游帳號被盜,推送了惡意映像檔,你會自動執行它。

如果上游的 CI/CD 被供應鏈攻擊(例如依賴套件被投毒),你會自動執行被污染的版本。

  • 如果上游不小心把測試版推上 latest,你會自動幫他們做 beta 測試。
  • 這不是危言聳聽。Docker Hub 上曾經發生過惡意映像檔事件,也有一些專案因為 CI 設定錯誤而把開發版推上正式標籤。自動更新等於把這些風險的「觸發」也自動化了。

    相對地,如果你使用固定版本標籤(例如 v1.2.3),Watchtower 基本上不會動作,因為 digest 沒變。但很多人就是為了「自動更新」才用 latest,這就形成一個矛盾:你想要的便利,正好建立在最不安全的標籤策略上。

    風險五:缺乏回滾機制與事故處理能力

    即使前面四項風險你都僥倖躲過,還有一個結構性問題:Watchtower 的設計裡,回滾不是內建功能。

    當自動更新導致服務異常時,你需要的是一個明確的「回到上一版」路徑。但 Watchtower 在更新時,通常不會保留舊映像檔的參照(雖然 Docker 本身會保留 dangling image 一段時間),也不會記錄「更新前是哪個 digest」。你只能靠自己的記憶、Docker 的 image 歷史,或者備份來還原。

    而在事故發生的當下,你往往處於資訊劣勢:你不知道更新是什麼時候發生的、不知道更新了哪個版本、不知道新版本改了什麼。這些資訊在手动更新時是自然而然會知道的(因為是你自己執行的),但在自動更新時全部消失。

    這就是自動化最陰險的地方:它省下的時間,會在事故發生時加倍討回來。

    四、實測環境與測試方法

    紙上談兵不夠,接下來說明筆者的實測設計,讓後面的結果有依據。

    測試環境規格

    測試在一個獨立的虛擬機上進行,避免影響正式服務。環境如下:

    主機:Proxmox VE 上的 VM,4 vCPU、8GB RAM、100GB SSD

    作業系統:Ubuntu Server 24.04 LTS

    Docker Engine:27.x,使用預設的 overlay2 儲存驅動

    Docker Compose:v2(docker compose 語法)

    測試期間:連續四週,每天檢查一次日誌與容器狀態

    測試的服務涵蓋三種典型類型:

  • A 類:無狀態 Web 服務(以一個靜態網站容器代表,更新頻繁但風險低)
  • B 類:有狀態應用(以一個使用 SQLite 的筆記應用代表,更新時會執行資料庫遷移)
  • C 類:資料庫容器(以 PostgreSQL 代表,更新涉及版本升級與資料目錄相容性)
  • 測試案例設計

    測試分成兩組:

    第一組:Watchtower 全自動模式。設定每 10 分鐘檢查一次(為了加速測試,實務上不建議這麼頻繁),不設定排除清單,所有測試容器都納入監控,並開啟 Email 通知以便記錄事件。

    第二組:Diun 純通知模式。同樣每 10 分鐘檢查一次,設定 Telegram 通知,並在收到通知後以人工方式執行更新。

    兩組使用相同的 Compose 檔與相同的服務,分別觀察「更新觸發的準確性」、「更新後的服務狀態」、「事故發生時的處理成本」三個面向。

    另外,為了模擬「上游推送破壞性更新」,筆者在測試期間手動建置了兩個模擬映像檔:一個會在啟動時故意讓資料庫遷移失敗,另一個會改變環境變數名稱。這兩個映像檔會推送到本地 registry,讓測試工具偵測到「有更新」。

    五、Watchtower 實測結果

    以下結果基于上述測試環境,盡量以實際觀察到的事件描述。

    情境一:無狀態 Web 服務更新(A 類)

    這是 Watchtower 表現最好的場景。四週內 A 類服務被自動更新了 6 次,每次都在一分鐘內完成,服務中斷時間約 5 到 10 秒。日誌顯示 Watchtower 正確地保留了原本的網路設定與掛載,新容器啟動後健康檢查通過。

    但有一個細節值得注意:其中一次更新後,容器內的時區設定變成 UTC(原本是 Asia/Taipei)。原因是上游映像檔在某個版本調整了基底映像檔,預設時區跟著改變。Watchtower 完全沒有意識到這件事,因為它只負責「換映像檔」,不負責「驗證行為」。這個問題直到隔天筆者看日誌時間戳才發現。

    這說明即使是最單純的無狀態服務,自動更新也可能帶來「非破壞性但惱人」的行為改變。

    情境二:有狀態應用與資料庫容器更新(B 類、C 類)

    這兩類是重災區。

    B 類應用在第二週遇到一次上游推送的「功能更新」,該版本修改了資料庫 schema,並在啟動時執行遷移。Watchtower 偵測到新映像檔後自動更新,新容器啟動、遷移執行、服務看似正常。但兩天後筆者發現部分筆記的標籤關聯消失。查看日誌才發現遷移過程中有幾筆資料因為外鍵約束而未被正確轉換。由於舊版程式已經被替換,且遷移是單向的,只能從備份還原。

    C 類的 PostgreSQL 更嚴重。第三週上游推送了一個小版本更新(16.x 內的修補),Watchtower 自動更新。新容器啟動時嘗試讀取舊的資料目錄,但因為某個內部格式變更,資料庫進入 recovery 模式,拒絕接受連線。Watchtower 沒有偵測到健康檢查失敗後的回滾需求,容器就這樣處於「執行中但無法提供服務」的狀態。直到筆者手動介入,把映像檔改回舊版標籤並重建容器,服務才恢復。

    附帶一提,Watchtower 在這次事件中有發送 Email 通知,但通知內容只是「Container updated successfully」,完全沒有提到健康檢查失敗。通知的語意與實際狀態不符,反而增加了誤判風險。

    情境三:更新失敗與容器消失事件

    測試後半段,筆者刻意推送了「啟動即失敗」的模擬映像檔。Watchtower 的行為如下:它偵測到新映像檔、停止舊容器、拉取新映像檔、建立新容器。新容器啟動後立即 crash,進入 restart loop。Watchtower 並沒有回復舊容器,也沒有再次通知(因為它的通知邏輯是「更新完成」,不是「服務健康」)。

    結果是:服務完全中斷,且沒有任何主動告警。如果不是筆者每天檢查,可能要等到使用者反應才會知道。這暴露了 Watchtower 在事故處理上的結構性缺陷:它是一個更新工具,不是一個可用性監控工具。它假設「更新 = 成功」,但現實中更新與成功之間有很大的距離。

    六、Diun 實測結果

    Diun 的測試結果相對單純,因為它本來就不負責更新,所以「失敗」的定義也不同。

    通知管道與可讀性

    Diun 的 Telegram 通知在四週內運作穩定,每次偵測到新映像檔都會發送一則訊息,內容包含映像檔名稱、舊 digest、新 digest、以及一個可點擊的連結(如果有設定)。相比 Watchtower 的通知,Diun 的訊息更接近「資訊」,而不是「結果」。它告訴你「有什麼變了」,而不是「我做了什麼」。

    這聽起來好像沒什麼,但在實務上差異很大。當你收到「有新版本」的通知時,你會自然地去看 release notes、評估風險、決定要不要更新。這個「停頓」就是安全性的來源。

    Diun 也支援 --providers 與 label 過濾,可以精準指定要監控哪些容器。筆者在測試中設定只監控 A、B、C 三類服務,排除 Watchtower 自身與其他基礎設施容器,運作正常。

    設定與維護成本

    Diun 的設定比 Watchtower 多一些,主要差別在通知管道的設定。以 Telegram 為例,需要建立 bot、取得 token、取得 chat ID,然後寫進 Compose 的環境變數。大約十分鐘可以完成。之後的維護成本主要是「你必須真的看通知」。

    在測試中,筆者刻意讓部分通知「已讀不回」,模擬真實使用情境。結果很明顯:累積三天的未處理通知後,心理上會開始有壓力,而這種壓力正是推動你去做「人工更新」的動力。換句話說,Diun 把「更新」從「自動背景任務」變成「待辦事項」,而待辦事項是會被完成的。

    從風險管理的角度來看,這其實是優點。但從「省事」的角度來看,這確實不如 Watchtower。

    七、實務建議:如何安全地使用自動更新

    看完上面的風險與實測,結論並不是「不要用自動更新」,而是「要用對方式」。以下提供三組建議,依序從架構到設定到流程。

    建議一:以 Diun 為主、Watchtower 為輔的混合架構

    最實用的做法,是把兩者搭配使用,但角色分明:

  • Diun 負責「發現」:監控所有容器,只要有新版本就通知你。這是你掌握系統狀態的主要來源。
  • Watchtower 負責「執行」:但只針對無狀態、低風險的服務開啟自動更新,並且設定排除清單,把資料庫、有狀態應用、以及任何你不想被自動替換的容器排除在外。
  • 這樣的架構下,Watchtower 的便利性用在對的地方(靜態網站、反向代理、監控工具),而它的風險被限制在可控範圍。Diun 則補上「資訊透明度」,讓你隨時知道有哪些服務落後。

    具體的 Watchtower 設定可以這樣寫:

    services:

    watchtower:

    image: containrrr/watchtower:latest

    container_name: watchtower

    restart: unless-stopped

    volumes:

    • /var/run/docker.sock:/var/run/docker.sock

    environment:

    • TZ=Asia/Taipei
    • WATCHTOWER_CLEANUP=true
    • WATCHTOWER_LABEL_ENABLE=true
    • WATCHTOWER_SCHEDULE=0 0 4 * * *
    • WATCHTOWER_NOTIFICATIONS=email
    • [email protected]
    • [email protected]
    • WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.example.com
    • WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587

    注意:預設不監控任何容器,需以 label 明確開啟

    關鍵在於 WATCHTOWER_LABEL_ENABLE=true。開啟後,Watchtower 只會更新帶有 com.centurylinklabs.watchtower.enable=true 標籤的容器。如此一來,預設是「不更新」,你必須主動為每個容器決定是否納入。這種「預設關閉」的設計,比預設全開安全得多。

    相對地,Diun 的設定可以這樣寫:

    services:

    diun:

    image: crazymax/diun:latest

    container_name: diun

    restart: unless-stopped

    volumes:

    • ./data:/data
    • /var/run/docker.sock:/var/run/docker.sock

    environment:

    • TZ=Asia/Taipei
    • DIUN_WATCH_SCHEDULE=0 */6 * * *
    • DIUN_PROVIDERS_DOCKER=true
    • DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true
    • DIUN_NOTIF_TELEGRAM_TOKEN=your_bot_token
    • DIUN_NOTIF_TELEGRAM_CHATIDS=your_chat_id

    Diun 的 WATCHBYDEFAULT=true 讓它監控所有容器,這與 Watchtower 的「預設不更新」形成互補:Diun 負責全面監控,Watchtower 只負責少數明確授權的容器。

    建議二:Watchtower 的關鍵安全設定

    如果你決定使用 Watchtower,以下幾個設定強烈建議開啟:

  • 固定更新時間:用 WATCHTOWER_SCHEDULE 指定在凌晨等低流量時段更新,避免白天服務中斷。預設的「每 24 小時」是從容器啟動時間起算,行為較難預測。
  • 清理舊映像檔:WATCHTOWER_CLEANUP=true 會在更新後移除舊映像檔,節省空間。但要注意,這也意味著你少了一個「回滾到舊版」的選項。如果你重視回滾能力,可以關閉這個選項,並定期手動清理。
  • 健康檢查與通知:Watchtower 本身不檢查服務健康,但你可以搭配外部監控(例如 Uptime Kuma)在更新後確認服務可用性。Watchtower 的通知只代表「更新動作完成」,不代表「服務正常」。
  • 排除清單:除了用 label 控制,也可以用 WATCHTOWER_DISABLE_CONTAINERS 明確排除容器名稱。雙重保險。
  • 還有一個常被忽略的點:不要把 Docker socket 掛載給不信任的容器。Watchtower 需要 /var/run/docker.sock 才能操作容器,這等於給它主機的完整控制權。如果 Watchtower 本身被入侵,攻擊者可以透過 socket 控制所有容器。因此 Watchtower 的映像檔來源要固定、要定期檢查,不要隨便使用來路不明的 fork。

    建議三:建立更新與回滾的標準流程

    工具只是輔助,流程才是根本。以下是筆者建議的更新 SOP:

  • 收到 Diun 通知後,先看 release notes:確認是否有破壞性變更、是否需要手動遷移、是否有已知問題。這一步大約花 2 到 5 分鐘,但能避免大多數事故。
  • 更新前先備份:對於有狀態服務,備份資料目錄或資料庫 dump 是底線。如果備份自動化還沒做好,先別更新。
  • 在低流量時段手動更新:執行 docker compose pull 與 docker compose up -d,觀察日誌 5 到 10 分鐘,確認健康檢查通過、服務可訪問。
  • 保留舊映像檔標籤:在 Compose 檔中使用固定版本標籤(例如 postgres:16.4),更新時改成新標籤。這樣回滾時只要改回舊標籤即可,不需要猜測 digest。
  • 記錄更新日誌:簡單記下「日期、服務、舊版、新版、結果」,未來排查問題時會很有用。
  • 這套流程聽起來很麻煩,但實際上每個服務的更新可能只需要五分鐘。一週花半小時維護十個服務,換來的是「知道自己在做什麼」的安心感。而 Watchtower 的自動更新,省下的正是這半小時,代價則是不確定性。

    八、結論:自動化不是放手不管

    Watchtower 與 Diun 的比較,表面上是在比「哪個工具比較好用」,但實際上是在問一個更根本的問題:你願意用多少掌控權,換取多少便利?

    Watchtower 把「更新」這件事完全自動化,適合無狀態、低風險、可快速回復的服務。它的價值在於「不會忘記」,但它的風險在於「不會判斷」。當更新出問題時,它不會幫你踩煞車,也不會幫你回滾。它只會繼續執行下一個排程。

    Diun 把「發現」自動化,把「決定」留給人。它的價值在於「資訊透明」,但它的成本在於「你必須真的去處理」。它不會讓你的服務自動保持最新,但它會讓你清楚知道每一個落後的版本,以及每一次更新的決定權都在你手上。

    如果你問筆者怎麼選,答案會是:以 Diun 作為主要的監控與通知工具,Watchtower 只用在明確標記的無狀態容器上,並且關閉預設監控、開啟 label 控制、設定固定排程與通知。這樣的組合,既能享受自動化的便利,又能把風險限制在可接受的範圍。

    容器自動更新不是不能做,而是不該「無腦做」。真正成熟的維運,不是把所有事情都交給機器,而是讓機器處理重複的部分,讓人處理判斷的部分。Watchtower 與 Diun 的差異,說到底就是這句話的兩種實踐方式。

    希望這篇實測與分析,能幫你在「方便」與「安全」之間,找到屬於自己的平衡點。畢竟,自架服務的樂趣,不就在於「什麼都自己來」嗎?那就連「更新」這件事,也好好地自己來吧。

    🏠 返回首頁