你用过docker部署前端项目吗?Tell Me Why 为何要用docker部署前端项目呢?

需求开发场景

在说docker之前,我们先来看看一般的需求开发和部署场景,是否需要安装node

需求开发部署场景

开发环境,我们使用windows或mac,开发前端项目,正常来说,都是要安装好对应node版本 ,使用node提供的npm包管理(构建)工具【除非是一个简单的只有hello world的html文件不用node】

生产环境,要发布到服务器上

  • 1. 静态SPA单页面应用部署
  • 服务器上不需要安装nodejs,使用使用nginx代理一下请求即可
  • 就算是多个单页面项目,我们在本地开发使用nvm管理一下node版本(比如有node12的老项目,也有node24的新项目)
  • 打包的dist,丢到服务器上后,依旧不需要在在服务器上安装node

  • 2. SSR服务端渲染部署
  • 除了静态SPA以外,我们也可能也要去写SSR应用
  • SSR实际上就是通过nodejs运行环境,在服务器上执行js代码
  • 比如解析路由、发请求拿后端数据、拼接生成html返回给前端的浏览器请求
  • 因此,SSR服务端渲染的生产环境的部署,服务器上,必须安装nodejs(当然,也要使用到nginx代理请求)
  • 如果,某个服务器上,只是部署一个SSR项目还好,我们只需要安装对应的node版本
  • 但是,如果有两个甚至多个SSR项目,且对应的node版本不一致(如需要node12和node24版本)
  • 那么,我们就需要在服务器上,安装nvm进行node版本的管理,不同的node版本前端项目,使用nvm切换到对应的node版本,然后,npm start跑起来项目【可使用pm2管理多进程】

  • 3. BFF中间层部署
  • BFF中间层,相当于一个服务层(中间层)
  • 就是,把后端的 "通用接口" 转化为前端的 "专属接口"
  • 比如,使用Express/Koa启一个服务(依赖node)(当然,也要使用到nginx代理请求)
  • 流程:用户 → 前端应用(PC/移动端/小程序) → BFF 中间层 → 后端服务 → 去数据库捞数据
  • 同样的,这个情况,和上述一样
  • 安装nvm进行node版本的管理,不同的BFF中间层,要切换对应node才能跑起来【可使用pm2管理多进程】

Web前端常见的三种部署方式

  1. 对于Vue或React的单页面应用,打包的dist静态资源,再搭配nginx
  2. 对于ssr(服务器渲染)或者bff(接口中间层),使用nvm管理node版本,同时使用pm2统一管理,再搭配nginx
  3. 使用docker镜像技术,一次构建,到处运行(最灵活的方式),基本上适合所有的前端部署方式。(辅以nginx)

接下来,说说docker镜像部署的好处

docker镜像部署前端项目的好处

1. 彻底解决环境一致性,不用再使用nvm做node版本切换

初学者,可以把docker容器镜像理解成:一个依赖宿主机的、封闭的、'微型'服务器的内存运行环境空间吗(非虚拟机那么冗余、且凭借宿主机的操作系统内核可在此内存运行环境空间跑服务程序)

  • 假设多个ssr或者bff且node版本依赖不同的项目,有几个,就打包几个镜像
  • 可以对应依赖的node版本等打包到镜像里面(当然nginx也可以选择连带着打包到镜像里面)
  • 镜像与镜像之间是独立的,虽然node依赖版本不一样,但是ssr的服务不会和bff的服务产生冲突
  • 不用再想以前那样,还得额外注意node版本的切换管理
  • 收益,比较明显!!!

2. 实现 "一次构建,各个服务器环境上都能直接运行"

无论是Linux还是Windows服务器,都能运行打包好的镜像(可移植性强)

  • 假设,有一天,原来的生产服务器爆炸了、死机了、因不可抗力直接挂了。
  • 老板赶紧买了一台新的服务器,让把原来生产的前端项目,移植到新的服务器上,越快越好。
  • 传统情况就是,在新的服务器上装各个版本的node,安装nvm,再打包使用pm2管理部署(耗时,约为一个小时)
  • 但是,如果是使用docker,直接把原本的镜像,复制粘贴到新服务器上即可(耗时约10分钟)
  • 收益,十分明显!!!

3. docker版本管理与回滚更简单方便安全

  • 假设新项目上线后发现bug,需紧急切回上一版本
  • 若是传统项目部署方式,需要本地git回滚,再重新打包,再发布到服务器上(耗时5分钟)
  • 若是docker镜像部署方式,直接使用其自带的版本标签tag管理
  • 每个版本的项目对应一个镜像标签(如 v1.0.0v1.0.1),标签与代码版本一一对应,可追溯回滚
  • 回滚时,只需停止当前容器,用旧版本标签的镜像重新启动容器(如 docker run my-app:v1.0.0
  • 整个过程秒级完成,且不会影响当前文件(容器销毁后文件自动清理),安全可靠(耗时1分钟)
  • 收益,十分明显!!!

4. docker部署流程标准化、自动化

  • 传统前端部署流程通常是
  • 本地打包 → 用winscp等工具传文件到服务器 → 然后手动配置ngixn或者手动启动node服务
  • 步骤繁琐且依赖人工操作,有一定概率手抖了,人工操作出问题(虽然概率不大,但也是一个隐患,假设概率千分之一)
  • 如果使用docker搭配cicd持续集成工具可以将部署流程自动化
  • 实现 代码在gitlab提交后 → 点一点,就能够自动构建镜像 → 然后自动推送到镜像仓库 → 最后服务器自动拉取镜像并重启容器
  • 全程无需人工干预,基本不会出问题(假设出问题概率百万分之一)
  • 节省人工操作时间、隐患概率大大降低
  • 收益,十分明显!!!

如果只是简单的spa单页面应用的部署,且暂时没有cicd工具的公司项目,也可以自己搞一个效能工具,类似于cicd的自动化发布脚本,参考笔者的这篇文章:juejin.cn/post/733054...
实际上,因为前端部署项目的依赖比较少,主要是可能依赖node环境,如果是后端部署项目,那依赖的就多了,使用docker技术的优势,能够进一步加大的明显体现出来

记录docker部署一个简单的单页面spa前端项目

提前启动电脑的虚拟化、并下载安装 Docker Desktop、并启用 WSL 2

首先,windows电脑的虚拟化要开启的,如下图

按下Ctrl + Shift + Esc 打开任务管理器、然后选择 性能 标签

然后,打开 powershell 执行 wsl --list --online,查看可安装的linux发行版,初始条件下,肯定是没安装的,然后执行 wsl --install 自动安装默认linux发行版(笔者用的是Ubuntu)

再然后,就是安装完成后重启电脑,使 WSL2 生效

最后,访问 Docker 官网 下载适用于 Windows或者MAC 的安装包,这样docker的前置准备工作,就做好了

本文不做安装的赘述,可以另行查阅文章,实践安装(若是网络不行,就使用小梯子吧)

单页面应用的docker打包

  • 假设,笔者有一个vue或者react项目
  • 这个项目最终,打包成了一个html文件,如下:
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>每天学点新知识------docker</h1>
</body>
</html>

编写Dockerfile文件

  • 想要使用docker打包镜像,就得告诉docker,这个镜像要打包那些东西
  • 本案例中,是打包一个单纯的html文件
  • 同时,还要告诉docker,有哪些依赖也需要连带着打包进去
  • 本案例中,打包单纯的html文件不太够用
  • 还需要搭配nginx(把nginx也打包进镜像中去)
  • 没办法,单纯的静态文件,没法自己提供网页服务,必须搭配一个 "服务器软件"
  • 这样打包出来的镜像,就像一个 "自带服务器的小盒子",不管放到哪台装了 Docker 的服务器上的机器上,都能直接跑起来

本案例的Dockerfile文件的编写很简单,就四行

  • FROM nginx:alpine
  • COPY index.html /usr/share/nginx/html/
  • EXPOSE 80
  • CMD ["nginx", "-g", "daemon off;"]

注释,释义如下

bash 复制代码
# 默认从 Docker Hub上下载基于Alpine Linux的轻量级版本的nginx,当执行docker打包镜像命令后,流程是:
# 自己windows电脑的命令行会触发Docker Desktop依据 WSL2 Linux内核从而下载nginx:alpine到WSL2文件系统
# 自己的nginx:alpine会下载到C:\Users\lss13\AppData\Local\Docker\wsl\disk文件夹中
# 有一个docker_data.vhdx硬盘映像文件
# 文件很大,类似压缩包,包含很多东西,其中就有下载的nginx:alpine镜像,也有构建出的新镜像和以往构建的老镜像
FROM nginx:alpine

# 将当前目录下的HTML文件复制到镜像中的/usr/share/nginx/html/目录
# 镜像最终存储在docker_data.vhdx虚拟磁盘中
# /usr/share/nginx/html/这个文件夹路径,是nginx用来默认存放静态资源的路径(规定,不用去修改)
# 至此,镜像文件中,已经包含了nginx的一堆东西和html,当然还有别的docker的一堆东西
COPY index.html /usr/share/nginx/html/

# EXPOSE不会实际开放端口,单纯的语法,不写也行(NGINX默认就是80端口)
EXPOSE 80

# 启动nginx  -g是全局配置命令  daemon off关闭后台运行模式
# (能够确保 nginx 前台运行,避免容器启动后立即退出)
# 这个cmd指令,会被存放在镜像文件中,当镜像被丢到服务器上后
# 当在服务器上执行docker run这个镜像的时候,才会进一步触发镜像里面的这个cmd命令执行
# 才会让镜像中的nginx启动起来,有这样的web服务,才能访问到镜像里面的html文件
CMD ["nginx", "-g", "daemon off;"]

编写打包构建镜像的js脚本

构建镜像,就一个核心的命令:docker build -t ${IMAGE_NAME} .

  • docker build:Docker 的构建命令,告诉 Docker "我要根据 Dockerfile文件里面的内容去构建镜像了"。
  • -t ${IMAGE_NAME}:给镜像 "贴标签"(指定名称),比如 -t my-nginx 就会把镜像命名为 my-nginx${IMAGE_NAME} 通常是一个变量,实际使用时会替换成具体名称,比如 my-webapp:v1)。
  • .:指定 Dockerfile 所在的路径(. 表示 "当前目录"),Docker 会从这个目录找 Dockerfile 文件,并读取里面的构建步骤。

当然,现在的我的 html 和 Dockerfile 和 要准备编写的 打包构建镜像的js脚本 在同一个文件夹里面(同级目录)

主要的核心,就是使用node的child_process的execSync,在命令行执行docker命令:

js 复制代码
const { execSync } = require('child_process'); // 引入同步执行命令模块

const IMAGE_NAME = 'my-html-app'; // 给镜像起个名字

execSync(`docker build -t ${IMAGE_NAME} .`, { stdio: 'inherit' }); // 派发命令执行打包构建镜像

完整export-image.js脚本如下:

js 复制代码
const { execSync } = require('child_process'); // 引入同步执行命令模块
const fs = require('fs'); // 引入文件系统模块

console.log('📦 开始构建和导出Docker镜像...');

// 配置变量
const IMAGE_NAME = 'my-html-app';
const TAR_FILE = `${IMAGE_NAME}.tar`;

try {
    // 检查Dockerfile是否存在
    if (!fs.existsSync('./Dockerfile')) {
        console.error('❌ 找不到Dockerfile文件');
        process.exit(1);
    }

    // 检查index.html是否存在
    if (!fs.existsSync('./index.html')) {
        console.error('❌ 找不到index.html文件');
        process.exit(1);
    }

    console.log('🔨 正在构建Docker镜像...');
    
    // 构建Docker镜像
    execSync(`docker build -t ${IMAGE_NAME} .`, { stdio: 'inherit' });
    
    console.log('\n✅ 镜像构建成功,开始导出镜像...');
    
    // 删除旧的tar文件(如果存在)
    if (fs.existsSync(TAR_FILE)) {
        fs.unlinkSync(TAR_FILE);
        console.log('🗑️  已删除旧的镜像文件');
    }
    
    // 导出镜像
    execSync(`docker save -o ${TAR_FILE} ${IMAGE_NAME}:latest`, { stdio: 'inherit' });
    
    // 检查文件是否成功创建
    if (fs.existsSync(TAR_FILE)) {
        const stats = fs.statSync(TAR_FILE);
        const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
        console.log('\n✅ 镜像导出成功!');
        console.log(`📁 导出文件: ${TAR_FILE} 文件大小: ${fileSizeMB} MB`);
        console.log('\n📋 接下来:');
        console.log('1. 复制 my-html-app.tar 和 deploy-to-server.sh 到Ubuntu');
        console.log('2. 在Ubuntu上运行: ./deploy-to-server.sh');
        
    } else {
        console.error('❌ 镜像导出失败');
        process.exit(1);
    }
    
} catch (error) {
    console.error('\n❌ 操作失败:');
    console.error(error.message);
    process.exit(1);
}
  • 我们可以在命令行中,执行这个脚本,比如:node export-image.js
  • 也可以,写一个bat脚本,这样也行,直接./build.bat回车

build.bat

bat 复制代码
@echo off
echo Let's start building Docker images...
node export-image.js

执行构建脚本

构建出来的镜像产物

编写服务器发布镜像的shell脚本

逻辑很简单,就是把当前服务器上的,原来的容器镜像删除掉(如果有的话),然后,在执行docker run -d -p $PORT:80 --name $CONTAINER_NAME $IMAGE_NAME:latest 命令

上述命令释义:

  • docker run(Docker 启动容器的核心命令)
  • -d (后台运行模式detach 的缩写)
  • -p $PORT:80
    • -p是端口映射publish的缩写
    • $PORT 是笔者自己的服务器(宿主机)端口,我这里用20000端口,外部通过这个端口访问容器;
    • 80 是容器内部的端口(Nginx 默认监听 80 端口)
    • 意思是:把我服务器(宿主机)上的20000端口,和容器的80端口连起来,外部访问我服务器上的20000端口,就会被转到这个docker容器镜像的80端口,也就会访问到镜像里面的nginx,也就能够访问到镜像里面的对应目录/usr/share/nginx/html/中的index.html文件(就能看到对应内容了)

当然了 服务器不随便开放端口,这个20000端口,我不会开放,我只会使用我服务器上nginx,进行请求的转发到20000端口,如下 nginx 配置

bash 复制代码
# docker的demo
location /dockerDemo/ {
    proxy_pass http://localhost:20000/;
    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_set_header X-Forwarded-Proto $scheme;
}

部署脚本如下:

bash 复制代码
#!/bin/bash

echo "🚀 开始部署Docker镜像到服务器..."

# 配置变量(镜像名称在构建时指定,容器名称在运行时指定)
CONTAINER_NAME="html-app-container"
IMAGE_NAME="my-html-app"
TAR_FILE="${IMAGE_NAME}.tar"
PORT="20000"

# 检查tar文件是否存在
if [ ! -f "$TAR_FILE" ]; then
    echo "❌ 找不到镜像文件: $TAR_FILE"
    echo "请确保已将镜像文件复制到当前目录"
    exit 1
fi

echo "📁 找到镜像文件: $TAR_FILE"

# 停止并删除现有容器(如果存在)
echo "🛑 停止并删除现有容器..."
docker stop $CONTAINER_NAME 2>/dev/null || true
docker rm $CONTAINER_NAME 2>/dev/null || true

# 删除现有镜像(如果存在)
echo "🗑️  删除现有镜像..."
docker rmi $IMAGE_NAME:latest 2>/dev/null || true

# 导入镜像
echo "📥 导入Docker镜像..."
docker load -i $TAR_FILE

# 运行容器
echo "🚀 启动新容器..."
docker run -d -p $PORT:80 --name $CONTAINER_NAME $IMAGE_NAME:latest

# 检查容器状态
if [ $? -eq 0 ]; then
    echo ""
    echo "✅ 部署成功!"
    echo "📊 容器状态:"
    docker ps | grep $CONTAINER_NAME
    echo ""
    echo "🌐 访问地址:"
    echo "   直接访问: http://localhost:$PORT"
    echo "   通过nginx代理: https://ashuai.site/dockerDemo/"
    echo ""
    echo "📋 有用的命令:"
    echo "   查看日志: docker logs $CONTAINER_NAME"
    echo "   停止容器: docker stop $CONTAINER_NAME"
    echo "   重启容器: docker restart $CONTAINER_NAME"
else
    echo "❌ 容器启动失败"
    exit 1
fi

最后一步,在服务器上,部署构建好的镜像

把镜像文件和构建脚本都丢到服务器上,在对应文件夹中,执行部署脚本,如下图:

  • 这样,我们的构建好了的,镜像,就成功部署了
  • 由于笔者是通过nginx代理的
  • 所以,访问:ashuai.site/dockerDemo/
  • 就可以 看到对应的内容了

总结

docker镜像打包,好像看起来麻烦一点点,还得写Dockerfile,还得写构建脚本,和部署脚本啥的,但是它解决了环境版本一致性问题,bff和ssr不同node版本,毕竟管理起来,还是有些麻烦的,还有服务器更换,要是重新安装各种版本,那的确耗时。(打包前端项目,有点明显、打包后端项目十分明显)

如果打包bff,我们可以编写如下的Dockerfile

js 复制代码
# 基础镜像:用轻量的 Node.js 16 版本(alpine 版本体积小)
FROM node:16-alpine

# 创建工作目录(类似在服务器上建一个专门的文件夹放代码)
WORKDIR /app

# 复制 package.json 和 package-lock.json(先复制依赖文件,利用 Docker 缓存加速构建)
COPY package*.json ./

# 安装依赖(npm install 会根据 package.json 下载所需的库)
RUN npm install --production  # --production 只装生产环境依赖,减小体积

# 复制 BFF 源代码(比如 server.js、路由文件等)
COPY . .

# 暴露 BFF 服务的端口(假设我的BFF的服务监听3000端口)
EXPOSE 3000

# 启动命令:运行 BFF 服务
CMD ["node", "server.js"]

所以,docker的优势有:

  1. 环境版本依赖一致性、隔离性与安全性
  2. 简化团队配置协作
  3. 可快速部署与扩展
  4. 而且,有了docker以后,CI/CD就更加好操作了(更好实现,标准化和自动化)
  5. 首次一劳------------而后永逸

A good memory is better than a bad pen. Record it down...

相关推荐
飞询2 小时前
docker 部署 sftp
运维·docker
白鲸开源18 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
小Wang1 天前
npm私有库创建(docker+verdaccio)
前端·docker·npm
容器魔方3 天前
Bloomberg 正式加入 Karmada 用户组!
云原生·容器·云计算
muyun28003 天前
Docker 下部署 Elasticsearch 8 并集成 Kibana 和 IK 分词器
elasticsearch·docker·容器
Nazi63 天前
k8s的dashboard
云原生·容器·kubernetes
绿箭柠檬茶3 天前
Ubuntu 服务器配置转发网络访问
服务器·网络·ubuntu
风_峰3 天前
Ubuntu Linux SD卡分区操作
嵌入式硬件·ubuntu·fpga开发
傻傻虎虎3 天前
【Docker】常用帮忙、镜像、容器、其他命令合集(2)
运维·docker·容器