設 NODE_ENV=production 是為了讓執行時期更快,但它會讓 npm install 略過 devDependencies——而建置工具幾乎都住在那裡。
發布於 2026-09-06・約 4 分鐘
我的容器 Dockerfile 裡有一行看起來很正常的設定:
ENV NODE_ENV=production
然後在容器裡建前端專案:
$ npm install $ npm run build sh: 1: vite: not found
vite 明明在 package.json 裡。node_modules 也建出來了。
vite
package.json
node_modules
NODE_ENV=production 對 npm 來說等同於下了 --omit=dev。它會只裝 dependencies,跳過整個 devDependencies。
NODE_ENV=production
--omit=dev
dependencies
devDependencies
而一個典型的前端專案,devDependencies 裡放的是:
{ "dependencies": { "react": "19.2.6", "react-dom": "19.2.6" }, "devDependencies": { "vite": "7.3.3", "@vitejs/plugin-react": "5.2.0", "tailwindcss": "4.2.2", "marked": "^18.0.11", "highlight.js": "^11.12.0" } }
也就是所有負責把原始碼變成產出的東西。這個分類本身是對的:建置工具不該進 bundle,marked 和 highlight.js 在我這是 build 時就跑完的,訪客端一個位元組都不會載到。
marked
highlight.js
問題是 NODE_ENV=production 這個名字聽起來是在講「這是正式環境」,實際的效果卻是「不要裝建置工具」。而正式環境的產出,恰恰是要用建置工具做出來的。
修法一:安裝時明確要求包含 dev。
npm ci --include=dev
--include=dev 會蓋掉 NODE_ENV 的推斷。這是最小改動,適合你沒辦法動那個環境變數的時候(例如那個變數是平台設的)。
--include=dev
NODE_ENV
修法二:安裝時把 NODE_ENV 拿掉。
RUN NODE_ENV= npm ci RUN npm run build
只在那一行改掉,不影響容器其他地方。
修法三(多階段建置,我最後用的):
FROM node:22-slim AS build WORKDIR /app COPY package*.json ./ RUN npm ci # 這一段沒有 NODE_ENV=production COPY . . RUN npm run build FROM node:22-slim ENV NODE_ENV=production WORKDIR /app COPY --from=build /app/dist ./dist COPY package*.json ./ RUN npm ci --omit=dev # 執行時期才只裝 runtime 相依 CMD ["node", "server.js"]
建置階段裝全部,執行階段只裝 runtime。最終映像檔不會帶著 vite 和 tailwind,而建置階段拿得到它們。
因為 npm install 不會失敗。 它成功了,只是少裝了一半。錯誤發生在後面的 npm run build,訊息是「找不到 vite」,指向的是完全錯的方向——你會去看 vite 有沒有裝好、版本對不對、PATH 有沒有問題,而不是去看安裝那一步略過了什麼。
npm install
npm run build
因為那行 ENV NODE_ENV=production 通常是別人寫的、或很久以前寫的。 它出現在 Dockerfile 上半部,跟建置指令離很遠,讀 Dockerfile 的時候不會把兩件事連起來。
因為它在本機不會發生。 本機開發沒人會設 NODE_ENV=production,所以本機 build 一直都好好的。這是典型「只在 CI 或容器裡壞」的問題。
安裝完之後直接問:
$ npm ls vite
沒裝的話會回 (empty) 或報 missing。
(empty)
或者看安裝了幾個套件:
$ npm ci && ls node_modules | wc -l $ NODE_ENV= npm ci && ls node_modules | wc -l
兩個數字差很多就是了。
還有一個更直接的:
$ npm config get production $ node -p "process.env.NODE_ENV"
npm ci
npm ci 會照 package-lock.json 裝、而且會先砍掉整個 node_modules。CI 跟容器建置都該用 ci,因為它是可重現的;install 會在必要時更新 lock 檔,同一份程式碼在不同時間可能裝出不同版本。
package-lock.json
ci
install
這兩件事會疊在一起:用 npm install 加上 NODE_ENV=production,你可能會拿到一個「跟 lock 檔不一致、而且缺一半套件」的 node_modules,然後花很久才發現它不是「壞了」而是「本來就沒裝」。
標籤:Node.js、npm、Docker、建置、CI