點燈坊

學而時習之,不亦悅乎

如何使用 Node + Express 將 Vue 打包成 Docker Image ?

Sam Xiao's Avatar 2019-08-08

Vue CLI 提供 yarn build 將 HTML/CSS/JS 編譯到 dist 目錄下,我們可再利用 Node + Express 當 Web Server,自行擴充 Yarn Script,最後只要下 yarn docker 就可一鍵建立 Docker Image。

Version

macOS Mojave 10.14.5
WebStorm 2019.2
Node 12.7
Express 4.17.1
Yarn 1.17.3
Vue CLI 3.9.3
Vue 2.6.10
Docker Desktop for macOS 2.1.0.0 (36874)

Vue Project

使用 Vue CLI 建立 Vue project,並自行在根目錄新增或修改以下檔案:

  • dockerfile
  • package-node.json
  • app.js
  • package.json
  • docker-compose.yml

express000

dockerfile

FROM node:alpine
WORKDIR /usr/src/app
COPY package-node.json ./package.json
RUN yarn install
COPY app.js .
COPY dist ./dist
CMD [ "node", "app.js" ]

須先建立 dockerfile,才能產生 image。

第 1 行

FROM node:alpine

使用最新版 node:alpine 為基底建立 image。

建議使用 node:*-alpine 為 production image,size 會小很多,以 Node 12.7 為例,node:latest 為 907 MB,node:12-alpine 為 79.3 MB

第 2 行

WORKDIR /usr/src/app

設定 image 內的 /usr/src/app 為工作目錄。

第 3 行

COPY package-node.json ./package.json

package-node.json 複製進 image,且改名為 package.json

稍後會建立 package-node.json

第 4 行

RUN yarn install

根據 image 內的 package.json 執行 yarn install 安裝 Node 所需的 Express。

第 5 行

COPY app.js .

app.js 複製進 image。

app.js 為 Node 啟動 Express 所需檔案,非 Vue 部分

第 6 行

COPY dist ./dist

dist 目錄下所有內容複製到 image 內 dist

dist 為 Vue CLI yarn build 編譯後最後結果,稍後會建立

第 8 行

CMD [ "node", "app.js" ]

最後將使用 Node 執行 app.js 啟動 Express。

package-node.json

{
  "name": "vue",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "express": "^4.17.1"
  }
}

設定 Node 所需的 dependency 的 package.json,為了有別於 Vue 的 package.json,特別建立成 package-node.json,在 dockerfile 內的 COPY package-node.json ./package.json 才會改名為 pakcage.json

app.js

let express = require('express');
let path = require('path');

let app = express();
app.use(express.static(path.join(__dirname, 'dist')));

//Launch listening server on port 80
app.listen(80, () => console.log('app listening on port 80!'));

Node 的啟動檔,由此啟動 Express。

第 5 行

app.use(express.static(path.join(__dirname, 'dist')));

設定 dist 目錄為 Express 放置 HTML/CSS/JS 目錄。

package.json

{
  "name": "docker-vue",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "docker": "yarn build && docker build -t oomusou/vue-express:0.0.1 ."
  },
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.9.0",
    "@vue/cli-plugin-eslint": "^3.9.0",
    "@vue/cli-service": "^3.9.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "vue-template-compiler": "^2.6.10"
  }
}

Vue 的 package.json

第 9 行

"docker": "yarn build && docker build -t oomusou/vue-express:0.0.1 ."

新增 Yarn script,只要執行 yarn docker,就會先執行 yarn build,然後執行 docker build 建立 Docker image。

$ docker build -t oomusou/vue-express:0.0.1 .

使用 docker build 建立 oomusou/vue-express:0.0.1 image。

  • -t:加上 tag,其中 : 左側為 image 名稱,右側為版號;/ 左側為 Docker ID,右側為 image 名稱

docker-compose.yml

version: "3"
services:
  express:
    image: oomusou/vue-express:0.0.1
    restart: always
    ports:
      - "80:80"

建立執行 container 的 docker-compose.yml

  • image:使用剛建立的 oomusou/vue-express:0.001
  • restart:當 container crash 時,會自動重啟
  • ports:container 內的 80 port,對應到外部的 80 port

Build Image

$ yarn docker

先執行 yarn build 建立 dist 目錄,再執行 docker build 產生 image。

express001

$ docker images

已經建立 oomusou/vue-express:0.0.1,只比 node:12-alpine 大一點點,因為多了 dist 與 Express。

express002

Start Container

$ docker-compose up -d

使用 docker-compose up 啟動 container。

express003

Chrome

express004

http://localhost:80 成功執行 Vue。

Stop Docker Container

$ docker-compose down

使用 docker-compose down 結束 container。

express005

Appendix

Publish to Docker Hub

$ docker push oomusou/vue-express:0.0.1

使用 docker push 將 image 發布到 Docker Hub。

Image 須以 ID/image:tag 格式描述

express006

Pull from Docker Hub

$ docker pull oomusou/vue-express:0.0.1

發布到 Docker Hub 之後,其他人就可使用 docker pull 下載 image。

Image 須以 ID/image:tag 格式描述

express007

Save Image to Tarball

$ docker save oomusou/vue-express:0.0.1 | gzip > vue-express-0.0.1.tgz

使用 docker save 將 Docker image 匯出後壓成 tarball。

| 為 pipe,表示透過 gzip 壓縮,最後成為 vue-0.001.tgz

express008

Restore Image from Tarball

$ docker load < vue-express-0.0.1.tgz

使用 docker load 將 tarball 還原成 image。

express009

Conclusion

  • 傳統若想將 Vue 打包成 image,直覺會想到 Nginx,但其實 Node + Express 也可提供 HTTP service,如此前後端均以 JavaScript 技術完成
  • 若要將 Node 用在 production,記得使用 alpine 系列 image
  • 最後可將 image 發布到 Docker Hub 或存成 tarball,如此其他人就可直接使用 image 建立環境

Sample Code

完整範例可在我的 GitHub 上找到

Reference

Node, Dockerizing a Node.js web app
Shekhar Gulati, Dockerizing a Vue.js application