第一次用docker快速部署pm2和nuxt前端项目

背景

之前做项目一直使用单页应用方式部署前端,是时候迁移到服务端渲染框架了

今天不讨论怎么写SSR代码,通过几种服务端渲染方案比较,我还是倾向使用成熟框架nuxt

今天我们讨论下,怎么使用nuxt快速部署,要解决下面几个问题:

  • nuxt项目打包后使用nodejs监听http服务,动态渲染html页面,使用pm2运行nodejs服务端,这里放到docker容器里面,可以单节点获取集群模式

  • 前端接口转发到后端需要nginx,上面pm2已经有容器了,那nginx另外开一个容器吗,我渲染把pm2和nginx放到一个容器里面运行

  • 需要一个容器,要求是已经安装好 pm2 \ nginx \ pnpm 软件包

  • docker容器运行多个程序,这里是pm2和nginx,需要通过shell脚本启动,并且最后一个应用需要驻留前台,否则容器会直接退出

源码 https://gitee.com/rootegg/nuxtweb,使用效果看最后一章验证

认识 nuxt

新建项目

swift 复制代码
pnpm dlx nuxi@latest init <project-name>

打包

arduino 复制代码
pnpm run build

如下图,build 之后生成 .output 目录,测试初始化项目生成后能正常构建

思路

上面已经看到 .output 目录下有 server 和 public , 我没用 pnpm做包管理器,pm2运行server下nodejs服务端,用nginx做pm2和后端接口转发,将nginx的80端口抛出去

  • 第一步我们需要一个干净的docker容器,里面已经安装好 pnpm \ pm2 \ nginx

  • 第二步编写shell启动脚本同时启动nginx和pm2

  • 第三步拷贝 .output 打包后内容到容器中,分别运行 pm2 和 nginx

构建干净容器

所有的容器都需要来源一个初始容器,这里我们选择 alpine:3.19,上海时区,最终我已经构建好一个容器里面包含 node,pnpm,yarn, pm2, python3, nginx

bash 复制代码
/app # node -v
v20.12.2
/app # npm -v
10.5.0
/app # yarn -v
1.22.19
/app # pnpm -v
9.3.0
/app # pm2 -v
5.3.1
/app # python -V
Python 3.11.9
/app # nginx -v
nginx version: nginx/1.24.0
/app #

公开的可以直接用容器地址:

bash 复制代码
ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine

Dockerfile文件很长,具体可以看 gitee.com/rootegg/cic...

编写shell启动脚本

我们知道在Dockerfile里面CMD和ENTRYPOINT可以作为启动命令,启动需要的应用程序,比如

比如启动nginx

css 复制代码
FROM nginx

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

CMD中写的 nginx -g daemon off;,意思就是启动nginx容器,并 -g daemon off; 保留在前台不退出,否则容器会关闭

比如启动pm2

bash 复制代码
# 基础镜像用的我已经封装好的node镜像
FROM ccr.ccs.tencentyun.com/rootegg/node:21.7.3-alpine
LABEL description="from ccr.ccs.tencentyun.com/rootegg/node:21.7.3-alpine, extend python3 pm2"

# 安装python3,因为pm2需要python,并用阿里云镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add --update --no-cache curl make gcc jq py3-configobj py3-pip py3-setuptools python3 python3-dev ca-certificates g++

# 安装 pm2
RUN npm install pm2 -g

# 抛出端口
EXPOSE 80 443 43554 3000

# 启动
CMD ["pm2-runtime", "start", "ecosystem.config.js"]

CMD中用的pm2-runtime,而不是pm2启动,因为pm2-runtime是专门为容器开发的,为了保留在前台不关闭容器,否则用 pm2命令会不保留前台直接关闭容器

融合nginx和pm2

下面这些都已经放入到我公开的容器中了,只需要使用即可,这一章可以跳过,看最后怎么使用这个容器即可。

bash 复制代码
ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine

我们需要4个文件:

  • app.js 是 pm2 运动的nodejs文件
  • Dockerfile 是构建docker镜像文件
  • ecosystem.config.js 是pm2启动脚本
  • start.sh 是容器CMD启动脚本,同时启动nginx和pm2

app.js

这个文件没啥说的,就是nodejs启动http服务监听3000端口,返回 hello world 文字

ini 复制代码
const http = require(`http`);

const server = http.createServer((req, res) => {

  const response = "hello world";
  
  res.writeHead(200);
  
  res.end(response);
  
});

server.listen(3000, () => {
    console.log(`Server is running on http://localhost:3000/`);
});

Dockerfile

安装 nginx,pm2已经过了,增加另外三个文件内容,最后一句 CMD 就是启动脚本,在脚本里在启动nginx和pm2,因为CMD不能启动多个程序,虽然可以写多个CMD,但是只有最后一个CMD有效,所以只能通过启动shell脚本的方式来启动多个应用程序

bash 复制代码
FROM ccr.ccs.tencentyun.com/rootegg/node:20.12.2-pm2-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk add --update --no-cache nginx

RUN npm install -g pnpm

WORKDIR /app

COPY ./start.sh ./start.sh

COPY ./ecosystem.config.js ./ecosystem.config.js

COPY ./app.js ./app.js

RUN chmod 777 ./start.sh

CMD ["sh", "./start.sh"]

ecosystem.config.js

标准pm2配置文件

css 复制代码
module.exports = {
  apps : [{
    name   : "app",
    script : "./app.js"
  }]
}

start.sh

启动脚本,启动nginx,这里不能用 nginx -g daemon off;,因为nginx后面还有pm2,只有pm2要保留在前台,nginx要运行在后台,只有有一个应用是在前台,其他都要在后台运行

bash 复制代码
#!/bin/bash

nginx

pm2-runtime start ecosystem.config.js

正式使用封装的容器

新建Nuxt项目

刚才已经新建好项目,只需要在根目录增加一个 Docker 文件即可,其他都不用动

关键是使用 ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine,里面环境都已经安装好了

swift 复制代码
# compile stage
FROM ccr.ccs.tencentyun.com/rootegg/node:pnpm-9.3.0 as build-stage

WORKDIR /app

COPY package*.json pnpm-lock.yaml ./

RUN pnpm install

COPY . .

RUN pnpm run build

# production stage
FROM ccr.ccs.tencentyun.com/rootegg/node:21.7.3-pm2-nginx-alpine as production-stage

WORKDIR /app

COPY --from=build-stage /app/.output/ .

RUN echo -e "module.exports = { \n\
  apps: [{ \n\
    name: 'app', \n\
    exec_mode: 'cluster', \n\
    instances: 'max', \n\
    script: './server/index.mjs' \n\
  }] \n\
}" > ./ecosystem.config.js

RUN echo -e "server {  \n\
    listen       80; \n\
    location /api/ { \n\
        proxy_pass  http://172.16.0.10:8080/api/; \n\
    } \n\
    location / { \n\
        proxy_pass  http://127.0.0.1:3000/; \n\
    } \n\
    gzip on; \n\
    gzip_min_length 1k; \n\
    gzip_http_version 1.1; \n\
    gzip_comp_level 6; \n\
    gzip_types text/plain application/x-javascript text/css application/xml application/javascript; \n\
    gzip_vary on; \n\
    access_log  /var/log/nginx/access.log ; \n\
} " > /etc/nginx/http.d/default.conf

验证效果

上面新建nuxt项目后,只增加了一个Dockerfile文件,项目上传gitee仓库

源码 https://gitee.com/rootegg/nuxtweb

在服务器上构建项目

bash 复制代码
# 克隆源码
git clone https://gitee.com/rootegg/nuxtweb.git

# 进入文件夹
cd nuxtweb

# 构建镜像,test.com是随便取的
docker build -t test.com/nuxtweb:v1 .

# build成功后运行镜像
docker run -d -p 50080:80 test.com/nuxtweb:v1

# 查看容器状态
docker ps

在网页上,输入ip和50080端口,我们来测试下效果,成功

相关推荐
卸任4 小时前
Vibe coding工具使用心得
前端·ai编程
brien95 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance5 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M5 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写5 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李5 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易5 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马6 小时前
前沿视界
前端
kyriewen6 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易6 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员