問題

三個看起來無關的東西,其實是同一個形狀:

  1. 啟動時跑 sequelize.sync({ alter: true }),所以沒人發現有一張表從來沒有 migration
  2. npx sequelize-cli 在本地找不到就即時下載,所以沒人發現這個工具不在任何依賴區
  3. go install ...@latest,所以沒人發現 builder 的 Go 版本已經跟不上

便利機制把缺口即時補起來,於是缺口從來不會以「錯誤」的形式出現。 它只會在你拿掉那個便利機制的那一刻,或是外部世界變動的那一天,一次全部現形。

實例一:自動同步掩蓋了 migration 的缺口

那個專案的啟動流程是這樣的:

await testConnection();
await sequelize.sync({ alter: true }); // 自動建立/更新 tables(development)

註解寫著 development,但 production image 跑的是同一段程式。alter: true 的意思是 「比對 model 與實際 schema,自己決定怎麼改」——它可能加欄位,也可能砍欄位, 而且多副本同時啟動會各改各的。Sequelize 官方文件也直說 force 和 alter 可能是破壞性操作, 不建議用在 production,schema 應該交給 migration(Sequelize: Model Basics — Model synchronization)。

真正讓人發現問題的是數數:14 個 model、13 個 migration、12 個 createTable。 查 git log 才知道,有一張表的 model 是 4 月 7 日加的,而最後一個 migration 停在 4 月 3 日。 中間那四天的落差,四個多月來一直由 sync 在每次啟動時補上。

所以「拿掉 sync 改跑 migration」這件事不能直接做——直接拿掉,新環境會少一張表。 順序必須是:先補上缺的 migration,再拿掉自動同步。 補完之後起真服務驗過一次:14 個 migration 全部套用,那張原本沒有 migration 的表也建出來了。

實例二:npx 能跑,不代表依賴有宣告

package.json 裡有這樣的指令:

"db:migrate": "npx sequelize-cli db:migrate"

看起來很正常,而且它一直能跑。但 sequelize-cli 既不在 dependencies 也不在 devDependencies。

先把 npx 的行為講精確:它會先用專案本地已安裝的套件執行檔; 只有在要求的套件不在本地專案依賴裡時,才會把它裝進 npm 快取再執行,而且裝之前會先提示, 可以用 --yes/--no 略過提示(npm docs: npx)。 所以 npx sequelize db:migrate 這種寫法本身沒有錯——前提是 sequelize-cli 有在依賴清單裡、已經裝在本地。

這個專案的問題就出在那個前提不成立:本地沒有,npx 就去 registry 即時下載, 所以在有網路的開發機上永遠不會出錯。

這個洞在三個情境下會爆:容器內沒有 registry 存取、映像檔用 --omit=dev 安裝、 或是改成非 root 執行之後寫不進 npx 的快取目錄。 這輪剛好三個都碰上了——把 migration 接進啟動流程、容器改跑非 root。 修法是把 sequelize-cli 移進 dependencies。

判準:能不能跑起來,不能拿來證明依賴有宣告好。 要證明就看依賴清單,或在乾淨容器裡裝一次。

實例三:@latest 不只是 image tag 的問題

第三個實例是 Dockerfile 的 builder stage 用 go install go.k6.io/xk6/cmd/xk6@latest, 某天 xk6 跳版要求更新的 Go,沒有任何人改過檔案,build 就壞了。 完整經過和修法寫在 自架觀測棧掛在 :latest 的「後續(2026-09)」段落,這裡不重複。

放在這篇要補的是它跟前兩個的共同點:@latest 在每次 build 時即時決定版本, 和 sync 在每次啟動時即時決定 schema、npx 在每次執行時即時決定工具是同一件事。 而多數人檢查釘版時只看 FROM 那幾行,RUN 裡的 @latest、沒帶版本的 pip install、 curl | sh 抓的安裝腳本都會漏掉。

共同的判準

這三個的共同點是:有一個機制在執行期即時補上設定期該做好的事。

問自己兩個問題就能找出同類:

  1. 如果現在斷網、或是換一台乾淨的機器、或是外部服務發一個新版,這件事還會成立嗎
  2. 這個「自動」是在哪一個階段發生的——build 期、部署期、還是每次啟動

答案是「啟動時自動處理」的,幾乎都值得檢查一次。

怎麼預防再次發生

  • schema 由 migration 決定,sync 只在本機開發用,而且不要讓 production image 跑到它
  • 拿掉自動同步之前,先數 model 數與 migration 數對不對得上
  • 任何 npx/pipx/go run 的工具,要嘛進依賴清單,要嘛在 image build 期裝好
  • 釘版檢查不只看 FROM:go install ...@latest、npm i -g pkg、pip install pkg、curl | sh 都算; 只 grep :latest 的守門規則要擴大到這些形式

相關