Nuxt3 with docker Nginx deploy ( 二 )
此篇是 Nuxt3 with docker ( ㄧ ) 的後續
上篇文章已經把 Nuxt3 Docker 相關的檔案都弄好了,由於在 Server 部署部分不太可能直接把 3000 port 給客戶去使用,通常會加一層 web service 來 Proxy
我自己是使用 Nginx,所以此篇會著重在如何改成可以使用 Nginx 的版本
而文章的最後我們會有兩個 running 的 container,一個是 Nginx 一個是 Nuxt3
Source code
從第一篇而來的當前目錄結構
第一篇是屬於沒有 Nginx 的版本
Nginx container
開始正文,首先我們先處理 Nginx container 的部分,新增一個 folder 底下兩個檔案
Nginx 下的 Dockerfile
# nginx/Dockerfile
FROM nginx:1.19.2-alpine
EXPOSE 80
COPY nginx.conf /etc/nginx/nginx.conf
Nginx 下的 nginx.conf
註:以下非 docker 的 Nginx directive 就不解釋,下面是我自己調整過後來的 XD
#nginx/nginx.conf
events {
worker_connections 4096;
}
http {
# Rate limiting, be ware of this setting as it can block you out of your own site
limit_req_zone $binary_remote_addr zone=rate_limiting:10m rate=30r/s;
server {
# listen 80
listen 80 default_server;
listen [::]:80 default_server;
server_name localhost;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# Adjust buffer sizes and timeouts to handle client requests efficiently:
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
send_timeout 30s;
# Adjust keepalive to conserve server resources:
keepalive_timeout 65;
keepalive_requests 100;
# For security, disable server tokens to prevent Nginx from revealing its version in responses:
server_tokens off;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
location / {
# burst:
# This parameter allows bursts of up to 50 requests to be queued
limit_req zone=rate_limiting burst=50 nodelay;
# ❗❗❗❗❗❗
proxy_pass http://frontend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
這邊分享比較重要的這一行
proxy_pass http://frontend:3000;
首先當 User 訪問瀏覽器的時候 request 會先到 Nginx 的 container ,然後在proxy_pass 到當前 frontend 這 docker container,而 response 則是 vice versa
以此文的情境我們的 Nginx container 會透過這樣的方式把 reqeust proxy 到 Nuxt3 的 container
基本上 container 是透過 network interface 來溝通彼此
Nuxt3 container
設定完 Nginx 之後,接著來處理 Nuxt3 部分
首先我們會新增一個 prod.Dockerfile 還有修改原先的兩個 docker-compose
分別對應到 dev 跟 prod 版本的
Dev 版本的 docker-compose.yml
version: '3.8'
services:
frontend:
container_name: frontend
build:
# 新增這一行,指定到 Dockerfile folder 裡面的 Dockerfile
dockerfile: Dockerfile/Dockerfile
context: .
args:
- NODE_VERSION=${DOCKER_NODE_VERSION}
ports:
# 把本機的 port 跟 container 的 port 綁定
- '${DOCKER_HOST_MACHINE_PORT}:3000'
volumes:
- .:/app
Dev 版本的 Dockerfile
ARG NODE_VERSION
FROM node:${NODE_VERSION}-slim as base
WORKDIR /app
# By default, just keep the container running
CMD ["tail", "-f", "/dev/null"]
Dev 版本的兩個檔案幾乎跟之前一樣,只差別指定 Dockerfile 的路徑而已
Prod 版本的 docker-compose.prod.yml
version: '3.8'
services:
frontend:
container_name: frontend
restart: on-failure
build:
dockerfile: Dockerfile/prod.Dockerfile
context: .
args:
- NODE_VERSION=${DOCKER_NODE_VERSION}
ports:
# 把本機的 port 跟 container 的 port 綁定
- '${DOCKER_HOST_MACHINE_PORT}:3000'
tty: true
stdin_open: true
# nginx
nginx:
container_name: nginx
restart: on-failure
build: ./nginx
ports:
- '80:80'
這邊會在 prod 的版本新增新增 nginx service
Prod 版本的 Dockerfile
# ---
# Base stage
ARG NODE_VERSION
FROM node:${NODE_VERSION}-slim as base
MAINTAINER "example@gmail.com"
# This can influence how the application behaves, particularly in terms of logging, performance optimizations
ENV NODE_ENV=production
WORKDIR /app
# ---
# Build stage
FROM base as build
COPY ../package.json .
COPY ../yarn.lock .
# 安裝 yarn
# --prefer-offline
#
# use network only if dependencies are not available in local cache
#
# --frozen-lockfile
#
# don't generate a lockfile and fail if an update is needed
#
# --non-interactive
#
# do not show interactive prompts
#
# --production=false
#
# install devDependencies
RUN yarn install \
--prefer-offline \
--frozen-lockfile \
--non-interactive \
--production=false
COPY . .
RUN yarn build
# ---
# Run stage
FROM base
# Copy only the files needed to run the app
COPY --from=build /app/.output /app/.output
CMD [ "node", ".output/server/index.mjs" ]
上面會分成三個 staging
分別是 Base,Build 以及 Run,也就是所謂的 Multi-stage builds
其原理就是在 build 的過程中可能會產生一堆過渡期的檔案,不會被 copy 到最終的 image 裡面,這樣就可以讓最終被使用的 image 比較小達到優化的效能
拆解 Prod 版本的 Dockerfile 之 Base 的 staging
# ---
# Base stage
ARG NODE_VERSION
FROM node:${NODE_VERSION}-slim as base
MAINTAINER "example@gmail.com"
# This can influence how the application behaves, particularly in terms of logging, performance optimizations
ENV NODE_ENV=production
WORKDIR /app
拆解 Prod 版本的 Dockerfile 之 Build 的 staging
# ---
# Build stage
FROM base as build
COPY ../package.json .
COPY ../yarn.lock .
# 安裝 yarn
# --prefer-offline
#
# use network only if dependencies are not available in local cache
#
# --frozen-lockfile
#
# don't generate a lockfile and fail if an update is needed
#
# --non-interactive
#
# do not show interactive prompts
#
# --production=false
#
# install devDependencies
RUN yarn install \
--prefer-offline \
--frozen-lockfile \
--non-interactive \
--production=false
COPY . .
RUN yarn build
此 build staging 會透過 FROM statements 來重新 build 一個新的 image 叫 build
FROM base as build
拆解 Prod 版本的 Dockerfile 之 Run 的 staging
# ---
# Run stage
FROM base
# Copy only the files needed to run the app
COPY --from=build /app/.output /app/.output
CMD [ "node", ".output/server/index.mjs" ]
而最終我們只需要 .output 裡面的檔案,所以我們也只 Copy .output 裡面的檔案,這樣就回到上面所說的 Multi-stage builds 把最終的 image size 變小
結論
此兩篇文章只有一個目的,就是要完整走一遍從本機開發到 Server 部署的階段,然後不想要本機有任何套件管理器 ( yarn …etc ) 或者 node 安裝的情況。
雖然第一篇文章最終 prod 部署的版本會在此篇有大幅度的修正,但這也是我自己在 try & error 整個 Docker 的流程