客戶的後端要接電子報寄信,得多裝一個套件。想省事,我們直接對「正在跑」的開發容器下了 docker exec <容器> npm install <套件>。裝完那一刻看起來成功了——直到熱重載觸發,畫面吐出一行:
sh: 1: next: not found # 連 next 執行檔都不見了 Container travel-backend-web Exited (255)
容器起不來,說找不到 next;想再裝一次補救,換來 ENOTEMPTY: rename '.../drizzle-orm'。node_modules 進到一個誰都修不好的半毀狀態。
這個開發環境是很常見的組合:原始碼用 bind mount 掛進容器即時改,node_modules 則放在一個具名 volume 裡,避免被主機的空資料夾蓋掉。
問題就在這:npm install 不是「加一個套件」這麼單純,它會重新盤點整個依賴樹、搬移、重寫 node_modules。當我們對一個正在跑、正在讀 node_modules 的容器做這件事,安裝跑到一半的狀態被固化下來——.bin 底下的執行檔(含 next)不見了,還留下一堆半寫入的暫存目錄,於是連重裝都被 ENOTEMPTY 卡住。
node_modules 這種東西,不該在活著的容器裡「熱補」。把它當成一個建置步驟:先停容器,用一個一次性容器掛上同一個 volume、清空後重跑一次完整安裝,再把服務起回來。
# 停掉服務後,用一次性容器掛同一個 named volume、乾淨重裝 docker run --rm \ -v "$PWD":/app -v proj_node_modules:/app/node_modules \ -w /app node:22-slim \ sh -c 'rm -rf node_modules/* node_modules/.[!.]*; npm install'
更「正規」的做法:改好 package.json 後 docker compose build 重建映像檔,讓具名 volume 在下次啟動時從乾淨的映像檔重新填充。
next: not found、ENOTEMPTY);別在原地反覆 install,先清空再裝。docker exec npm install 看起來方便,但它把一個「建置動作」硬塞進執行期,還剛好塞在一個被 volume 遮蓋、又正在被讀取的資料夾上。把裝依賴當成重建映像檔或跑一次性容器,就不會再遇到「連 next 都不見了」。




