問題
三個看起來無關的東西,其實是同一個形狀:
- 啟動時跑
sequelize.sync({ alter: true }),所以沒人發現有一張表從來沒有 migration npx sequelize-cli在本地找不到就即時下載,所以沒人發現這個工具不在任何依賴區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 抓的安裝腳本都會漏掉。
共同的判準
這三個的共同點是:有一個機制在執行期即時補上設定期該做好的事。
問自己兩個問題就能找出同類:
- 如果現在斷網、或是換一台乾淨的機器、或是外部服務發一個新版,這件事還會成立嗎
- 這個「自動」是在哪一個階段發生的——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的守門規則要擴大到這些形式
相關
- 自架觀測棧掛在 :latest:同一根因的另一種表現(設定格式漂移),以及 xk6
@latest那次 build 失敗的完整經過 - 資料庫 Migration 策略:migration 工具與 rollback 的完整說明
- 寫了但沒接上:宣告了但沒接線;這篇反過來,是「沒宣告但一直能跑」