一份為 Node.js 應用準備的 Dockerfile 指南

lsvih發表於2018-02-28

一份為 Node.js 應用準備的 Dockerfile 指南

TL;DR

本文涵蓋了從建立簡單的 Dockerfile 到生產環境多級構建 Node.js Web 應用的例子。以下為本指南的內容摘要:

  • 使用合適的基礎映象(開發環境使用 carbon,生產環境使用 alpine)。
  • 在開發時使用 nodemon 進行熱載入。
  • 優化 Docker 的 cache layer(快取層)—— 按照正確的順序使用命令,僅在需要時執行 npm install
  • 使用 serve 包部署靜態檔案(比如 React、Vue、Angular 生成的 bundle)。
  • 使用 alpine 進行生產環境下的多級構建,減少最終映象檔案的大小。
  • #建議 — 1) 使用 COPY 代替 ADD 2) 使用 init 標識,處理 CTRL-C 等核心訊號。

如果你需要以上步驟的程式碼,請參考 GitHub repo

內容

  1. 簡單的 Dockerfile 樣例與 .dockerignore 檔案
  2. 使用 nodemon 實現熱更新
  3. 優化
  4. 部署靜態檔案
  5. 生產環境中的直接構建
  6. 生產環境中的多級構建

讓我們先假設一個名為 node-app 的應用,一個簡單的目錄結構。在頂級目錄下,包含 Dockerfile 以及 package.json,node app 的程式碼將存於 src 目錄下。為了簡潔起見,我們假設 server.js 定義了一個執行於 8080 埠的 node express 服務。

node-app
├── Dockerfile
├── package.json
└── src
    └── server.js
複製程式碼

1. 簡單的 Dockerfile 樣例

FROM node:carbon

# 建立 app 目錄
WORKDIR /app

# 安裝 app 依賴
# 使用萬用字元確保 package.json 與 package-lock.json 複製到需要的地方。(npm 版本 5 以上) COPY package*.json ./

RUN npm install
# 如果你需要構建生產環境下的程式碼,請使用:
# RUN npm install --only=production

# 打包 app 原始碼
COPY src /app

EXPOSE 8080
CMD [ "node", "server.js" ]
複製程式碼

我們將使用最新的 LTS 版本 node:carbon 作為基礎映象。

在構建映象時,docker 會獲取所有位於 context 目錄下的檔案。為了增加 docker 構建的速度,可以在 context 目錄中新增 .dockerignore 檔案來排除不需要的檔案與目錄。

通常,你的 .dockerignore 檔案件應該如下所示:

.git
node_modules
npm-debug
複製程式碼

構建並執行此映象:

$ cd node-docker
$ docker build -t node-docker-dev .
$ docker run --rm -it -p 8080:8080 node-docker-dev
複製程式碼

你將能在 [http://localhost:8080](http://localhost:8080.) 訪問此 app。使用 Ctrl+C 組合鍵可以退出程式。

現在,假設你希望在每次修改程式碼(比如在本地部署時)時都執行以上程式碼,那麼你需要在啟停 node 服務時將程式碼原始檔掛載到容器中。

$ docker run --rm -it -p 8080:8080 -v $(pwd):/app \
             node-docker-dev bash
root@id:/app# node src/server.js
複製程式碼

2. 使用 Nodemon 實現熱更新

nodemon 是一款很受歡迎的包,它在執行時會監視目錄中的檔案,當任何檔案發生了改變時,nodemon 將會自動重啟你的 node 應用。

FROM node:carbon

# 建立 app 目錄
WORKDIR /app

# 安裝 nodemon 以實現熱更新
RUN npm install -g nodemon

# 安裝 app 依賴
# 使用萬用字元確保 package.json 與 package-lock.json 複製到需要的地方。(npm 版本 5 以上)COPY package*.json ./

RUN npm install

# 打包 app 原始碼
COPY src /app

EXPOSE 8080
CMD [ "nodemon", "server.js" ]
複製程式碼

我們將構建映象並執行 nodemon,以便在 app 目錄下檔案發生變動時對程式碼進行 rebuild。

$ cd node-docker
$ docker build -t node-hot-reload-docker .
$ docker run --rm -it -p 8080:8080 -v $(pwd):/app \
             node-hot-reload-docker bash
root@id:/app# nodemon src/server.js
複製程式碼

一切在 app 目錄下的更改都會觸發 rebuild,發生的變化都能在 [http://localhost:8080](http://localhost:8080.) 上實時展示。請注意,我們已經將檔案掛載到了容器中,因此 nodemon 才能正常工作。

3. 優化

在你的 Dockerfile 中,除非你需要自動解壓 tar 檔案(參考 Docker 最佳實踐),否則最好使用 COPY 來代替 ADD。

繞過 package.jsonstart 命令,而是直接將 app “燒錄”至映象檔案中。因此在 Dockerfile CMD 中不要使用:

$ CMD ["npm","start"]
複製程式碼

而應當使用:

$ CMD ["node","server.js"]
複製程式碼

來代替。這樣可以減少在容器中執行的程式數量,同時還能讓 Node.js 程式接收到 SIGTERMSIGINT 等退出訊號,如若是 npm 程式則會無視這些訊號(參考 Node.js Docker 最佳實踐)。

你還可以使用 --init 標誌,用 tini 輕量集初始化系統來包裝你的 Node.js 程式,它們也能響應一些 SIGTERMCTRL-C)之類的核心訊號。例如,你可以使用:

$ docker run --rm -it --init -p 8080:8080 -v $(pwd):/app \
             node-docker-dev bash
複製程式碼

4. 部署靜態檔案

前文的 Dockerfile 是假設你執行了由 Node.js 構建的 API 服務。那麼下面說說如果你想要用 Node.js 部署 React.js、Vue.js、Angular.js 應用時該怎麼做。

FROM node:carbon

# 建立 app 目錄
WORKDIR /app

# 安裝 app 依賴
RUN npm -g install serve
# 使用萬用字元複製 package.json 與 package-lock.json
COPY package*.json ./

RUN npm install

# 打包 app 原始碼
COPY src /app
# 將 react、vue、angular 打包構建成靜態檔案
RUN npm run build

EXPOSE 8080
# 將 dist 目錄部署於 8080 埠
CMD ["serve", "-s", "dist", "-p", "8080"]
複製程式碼

如你所見,當你需要構建 React、Vue、Angular 製作的 UI app 時,使用 npm run build 來壓縮 JS 與 CSS 檔案,生成最終的 bundle 包,在這兒我們使用了 npm 的 [serve](https://www.npmjs.com/package/serve) 包來部署靜態檔案。

此外,可以使用一些替代方案:1) 在本地構建打包檔案,然後使用 nginx docker 來部署這些靜態檔案。2) 使用 CI/CD 工作流進行構建。

5. 生產環境中的直接構建

FROM node:carbon

# 建立 app 目錄
WORKDIR /app

# 安裝 app 依賴
# RUN npm -g install serve

# 使用萬用字元複製 package.json 與 package-lock.json
COPY package*.json ./

RUN npm install

# 打包 app 原始碼
COPY src /app

# 如需對 react/vue/angular 打包,生成靜態檔案,使用:
# RUN npm run build

EXPOSE 8080
# 如需部署靜態檔案,使用:
#CMD ["serve", "-s", "dist", "-p", "8080"]
CMD [ "node", "server.js" ]
複製程式碼

構建並執行這個一體化映象:

$ cd node-docker
$ docker build -t node-docker-prod .
$ docker run --rm -it -p 8080:8080 node-docker-prod
複製程式碼

由於底層為 Debian,構建完成後映象約為 700MB(具體數值取決於你的原始碼)。下面探討如何減小這個檔案的大小。

6. 生產環境中的多級構建

使用多級構建時,將在 Dockerfile 中使用多個 FROM 語句,但最後僅會使用最終階段構建的檔案。這樣,得到的映象將僅包含生產伺服器中所需的依賴,理想情況下檔案將非常小。

# ---- Base Node ----
FROM node:carbon AS base
# 建立 app 目錄
WORKDIR /app

# ---- Dependencies ----
FROM base AS dependencies  
# 使用萬用字元複製 package.json 與 package-lock.json
COPY package*.json ./
# 安裝在‘devDependencies’中包含的依賴
RUN npm install

# ---- Copy Files/Build ----
FROM dependencies AS build  
WORKDIR /app
COPY src /app
# 如需對 react/vue/angular 打包,生成靜態檔案,使用:
# RUN npm run build

# --- Release with Alpine ----
FROM node:8.9-alpine AS release  
# 建立 app 目錄
WORKDIR /app
# 可選命令:
# RUN npm -g install serve
COPY --from=dependencies /app/package.json ./
# 安裝 app 依賴
RUN npm install --only=production
COPY --from=build /app ./
#CMD ["serve", "-s", "dist", "-p", "8080"]
CMD ["node", "server.js"]
複製程式碼

使用上面的方法,用 Alpine 構建的映象檔案大小約 70MB,比之前少了 10 倍。使用 alpine 版本進行構建能有效減小映象的大小。

如果你對前面的方法有任何建議,或希望看到別的用例,請告知作者。

加入 Reddit / HackerNews 討論:)


此外,你是否試過將 Node.js web 應用部署在 Hasura 上呢?這其實是將 Node.js 應用部署於 HTTPS 域名的最快的方法(僅需使用 git push)。嘗試使用 hasura.io/hub/nodejs-… 的模板快速入門吧!Hasura 中所有的專案模板都帶有 Dockerfile 與 Kubernetes 標準檔案,你可以自由進行定義。

感謝 Tanmai Gopal


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章