前端使用 Docker 仅构建一次镜像实现本地最新打包产物运行

395 阅读5分钟

Docker 是一个开源的应用容器引擎,可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 linux 机器上。

在上家公司的时候经常会遇到线上环境的样式和本地开发环境不一致的情况,对于前端来说是不太好排查这种问题的,因为线上部署的是打包后的产物,并且线上环境必须维持稳定,基本上不可能让前端去线上环境调试,这个时候就只能尝试本地运行打包后的产物了,但是直接运行打包后的产物一定会出现很多问题,比如环境不一致、网络请求无法发送等等,所以我们在本地借助 docker 去模拟线上环境,然后借助 nginx 做网络转发,这样就可以实现在本地调试打包产物了。

关于 docker 的安装和使用可以参考下菜鸟教程,有非常详尽的介绍,这里就不再介绍了,只关注前端如何借助 docker 来运行打包产物。本文示例使用的前端项目是 github.com/dieshu-lx/x… ,后端是 github.com/dieshu-lx/x…

首先我们需要构建一次基础镜像,在根目录下新建 Dockerfile

# 使用 Node.js 20 作为构建环境
FROM node:20-alpine as builder

# 设置工作目录
WORKDIR /usr/src/app

# 复制 package.json 和 yarn.lock 文件
COPY package.json yarn.lock ./

# 安装依赖并清理缓存
RUN yarn install --frozen-lockfile && yarn cache clean

# 复制所有源代码
COPY . .

# 构建应用
RUN yarn build

# 使用轻量级的 Nginx 镜像作为生产环境
FROM nginx:alpine

# 从构建阶段复制构建结果到 Nginx 的静态文件目录
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html

# 复制自定义 Nginx 配置文件(如果有的话)
COPY nginx.conf /etc/nginx/nginx.conf

# 暴露 80 端口
EXPOSE 80

# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

Dockerfile 是一种文本文件,其中包含构建 Docker 镜像所需的一系列指令和参数。通过编写 Dockerfile,用户可以自动化和简化 Docker 镜像的构建过程,确保软件环境的一致性和可重现性。

本文中的后端是用 nest 起的一个服务,都是基于 node 构建,所以前后端使用 Dockerfile 基本上是一致的,这里不再赘述,感兴趣的可以拉源代码看看。

编写完 Dockerfile 文件之后,我们还需要配置一下 nginx 转发,转发前端请求到我们起的后端服务上,在根目录下新建 nginx.conf 文件

worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
  worker_connections 1024;
}

http {
  server {
    listen 80;

    server_name localhost;

    root /usr/share/nginx/html;

    index index.html;

    location /api/ {
      rewrite ^/api(/.*)$ $1 break;
      # 这里的 backend 指的是 docker-compose.yml 中定义的 backend 服务容器
      proxy_pass http://backend:3000;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host $host;
      proxy_cache_bypass $http_upgrade;
    }
  }
}

这里是根据后面的 docker-compose 服务编排来写的,因为前端服务是暴露在容器的 80 端口上的,后端服务在 3000 端口,所以这里是监听 80 端口上的请求,转发到了 backend 容器的 3000 端口上。

编写完这些文件之后就可以正式开始构建基础镜像了,这里为了方便,我写成了一个 script 放到 package.json 里面,直接运行 yarn run docker:build 就行。

image.png

这里构建的前端镜像名字是 xiaoou-frontend,后端构建的镜像名字是xiaoou-backend

构建完基础镜像之后就可以编排服务了,在根目录下新建 docker-compose.yml 文件

services:
  backend:
    #后端服务
    image: xiaoou-backend
    container_name: xiaoou-backend
    ports:
      - '3000:3000'
    command: ['node', 'dist/main.js']

  app:
    # 只在初次启动时构建基础镜像,后续启动时直接使用volumes挂载
    image: xiaoou-frontend
    container_name: xiaoou-frontend
    ports:
      - '10000:80'
    # 将本地build后的dist目录挂载到容器中,替换掉容器中默认的dist目录,
    # 这样在容器中运行时,会使用我们本地build后的dist目录
    # 理论上只需要构建一次,然后使用volumes挂载,后续只需要本地build生成dist目录即可,不需要重新构建镜像
    volumes:
      - ./dist:/usr/share/nginx/html
    # 确保后端服务先启动
    depends_on:
      - backend

这里编排了两个服务,一个是 backend 后端,一个是 app 前端,分别使用了我们之前构建好的两个镜像,将本地的 3000 端口映射到 backend 容器的 3000 端口,本地的 10000 端口映射到 app 容器的 80 端口上,这样就可以通过本地的 10000 端口和 3000 端口访问到容器内的前后端服务了。

!!!需要注意的是,这里的 volumes 是使用我们本地打包生成的 dist 目录下的文件去替换掉了 /usr/share/nginx/html 目录下的文件,所以这里其实是直接访问的我们的打包产物,而不是之前生成的基础镜像的内容 !!!

在写完 docker-compose.yml 文件之后,通过命令 docker-compose up 就能启动我们编排的服务了,在浏览器访问 http://localhost:10000 就可以直接访问到我们启动的服务了。

image.png

最后就是看看是否实现我们的 仅构建一次镜像实现本地最新打包产物运行 目的了。
首先我们修改源代码内容

image.png

然后运行yarn build重新打包生成最新的打包产物

image.png 最后重新访问页面 http://localhost:10000/ ,这时可以发现,我们的最新改动在页面上已经生效了。

image.png

总结

我们实现了标题提到的仅构建一次镜像实现本地最新打包产物运行的需求,通过借助 docker 容器,可以实现在本地调试打包产物,快速排查线上环境和我们本地开发环境上由于环境不一致等各种原因导致的异常。