點燈坊

學而時習之,不亦悅乎

如何使用 Nginx + Reverse Proxy 將 Vue 打包成 Docker Image ?

Sam Xiao's Avatar 2019-08-11

實務上會遇到有些 API 並沒有開放 CORS,此時可利用 Nginx 提供 Reverse Proxy,並自行擴充 Yarn Script,最後只要下 yarn docker 就可一鍵建立 Docker Image。

Version

macOS Mojave 10.14.5
WebStorm 2019.2
Nginx 1.17.2
Vue CLI 3.10.0
Vue 2.6.10
Docker Desktop for macOS 2.1.0.0 (36874)

Vue Project

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

  • dockerfile
  • default.conf
  • package.json
  • docker-compose.yml

nginx000

dockerfile

FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY default.conf /etc/nginx/conf.d/

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

第 1 行

FROM nginx:alpine

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

nginx:apline 為最新版為 Docker 最佳化的 image,size 較小

第 2 行

COPY dist /usr/share/nginx/html

dist 目錄下所有檔案複製到 image 內的 /usr/share/nginx/html 目錄下,此為 Nginx 放 HTML 之處。

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

第 3 行

COPY default.conf /etc/nginx/conf.d/

將 Nginx 設定檔 default.conf 複製到 image 內的 /etc/nginx/conf.d/ 目錄下。

default.conf

server {
  listen       80;
  server_name  localhost;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }

  location /api {
    proxy_pass http://express:3000;
  }
}

第 2 行

listen       80;
server_name  localhost;

location / {
  root   /usr/share/nginx/html;
  index  index.html index.htm;
}

error_page   500 502 503 504  /50x.html;
location = /50x.html {
  root   /usr/share/nginx/html;
}

為 Nginx 預設的設定,就不加以修改。

15 行

location /api {
  proxy_pass http://express:3000;
}

所有打到 http://localhost/api 的 request,都會導到 http://express:3000/api

因為 Nginx 與 API server 都在 Docker 內,因此 proxy_pass 的 host name 為 container 的 service name

package.json

{
  "name": "vue-nginx-proxy",
  "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-nginx:0.0.1 ."
  },
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.10.0",
    "@vue/cli-plugin-eslint": "^3.10.0",
    "@vue/cli-service": "^3.10.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-nginx:0.0.1 ."

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

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

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

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

docker-compose.yml

version: "3"
services:
  nginx:
    image: oomusou/vue-nginx:0.0.1
    restart: always
    ports:
      - "80:80"
    networks:
      - node-dev

  express:
    image: oomusou/node-express:0.0.1
    restart: always
    networks:
      - node-dev

networks:
  node-dev:

第 1 行

version: "3"
services:
  nginx:
    ...
  express:
    ...

docker-compose.yml 一共啟動兩個 service:

  • nginx:Nginx 提供 HTTP 與 reverse-proxy 服務
  • express:Node 提供 API 服務

第 3 行

nginx:
  image: oomusou/vue-nginx:0.0.1
  restart: always
  ports:
    - "80:80"
  networks:
    - node-dev

設定 nginx service:

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

11 行

express:
  image: oomusou/node-express:0.0.1
  restart: always
  networks:
    - node-dev

設定 express service:

  • image:使用剛建立的 oomusou/node-express:0.001 image
  • restart:當 container crash 時,會自動重啟
  • networks:與 nginx service 共用 node-dev network

express service 並沒有對 container 外部開放 port,因此外部無法使用 express 提供的 API 服務

Build Image

$ yarn docker

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

nginx001

Start Container

$ docker-compose up -d

使用 docker-compose up 啟動 container。

nginx002

同時啟動了 nginxexpress 兩個 service。

Chrome

nginx003

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

nginx004

使用 Postman 針對 http://localhost/api/hello-world GET 測試,可順利透過 Nginx 的 reverse proxy 打到 express service。

Stop Container

$ docker-compose down

使用 docker-compose down 結束 container。

nginx005

Apendix

可使用任何後端技術建立 API,在此以 Node + Express 為例建立 node-express image。

Node Project

使用 express-get 目錄,並自行在根目錄新增以下檔案:

  • dockerfile
  • app.js
  • package.json

dockerfile

FROM node:alpine
WORKDIR /usr/src/app
COPY package.json ./package.json
RUN yarn install
COPY app.js .
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:alpine 為 79.3 MB

第 2 行

WORKDIR /usr/src/app

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

第 3 行

COPY package.json ./package.json

package.json 複製進 image。

第 4 行

RUN yarn install

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

第 5 行

COPY app.js .

app.js 複製進 image。

第 6 行

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

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

app.js

let express = require('express');

let app = express();

app.get('/api/hello-world', (req, res) => res.send('Hello World'));

app.listen(3000, () => console.log('app listening on port 3000!'));

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

第 1 行

let express = require('express');

Import express module。

第 3 行

let app = express();

建立 app Express instance。

第 5 行

app.get('/api/hello-world', (req, res) => res.send('Hello World'));

建立 /api/hello-world GET,回傳 Hello World

第 7 行

app.listen(3000, () => console.log('app listening on port 3000!'));

啟動 Express 在 3000 port。

package.json

{
  "name": "express-get",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "express": "^4.17.1"
  },
  "scripts": {
    "docker": "docker build -t oomusou/node-express:0.0.1 ."
  }
}

設定 Node 所需的 dependency 的 package.json

第 5 行

"dependencies": {
  "express": "^4.17.1"
},

安裝 express package。

第 9 行

"docker": "docker build -t oomusou/node-express:0.0.1 ."

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

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

Conclusion

  • 與 Vue + Nginx 包成 Docker image 類似,唯必須將 Nginx 設定檔 default.conf 也包進 image,因為 reverse proxy 要在此設定
  • 使用 Node + Express 建立 API server 時,建議以 /api 開頭,如此可方便 Nginx 的 reverse proxy 設定

Sample Code

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