之前一直用 FTP 手动拖前端包到服务器,拖了几次实在忍不了——又慢又容易手滑覆盖错目录。后来用 Docker + Jenkins 搭了个自动化流水线,代码一推送,构建、打包、部署全自动跑完,几分钟搞定。踩了一些坑,记录一下顺带帮大家避坑。
环境很简单,一台有 Docker 的服务器就行。Jenkins 也用 Docker 起,这样最干净。启动命令长这样,关键是得把宿主机的 docker.sock 挂进容器,让 Jenkins 能操作宿主的 Docker:
docker run -d \--name jenkins \-p 8080:8080 -p 50000:50000 \-v jenkins_home:/var/jenkins_home \-v /var/run/docker.sock:/var/run/docker.sock \jenkins/jenkins:lts
跑起来之后 docker logs 捞一下初始密码,浏览器 ip:8080 进去装推荐插件,再手动把 NodeJS、Docker Pipeline 之类的装上。如果插件下载慢,建议上代理,不然等得心态爆炸。
重点是写 Pipeline,我们用 Jenkinsfile 把整个流程串起来。在项目根目录新建一个 Jenkinsfile,内容我先贴出来,再做说明:
pipeline {agent anyenvironment {// 镜像名,可以直接用项目名IMAGE_NAME = 'my-frontend-app'// 宿主机上部署的端口映射HOST_PORT = '8888'}stages {stage('Checkout') {steps {// 拉取代码,根据你的git配置调整git branch: 'main', url: 'https://github.com/yourname/your-repo.git'}}stage('Build Frontend') {steps {// 在node容器里构建前端script {docker.image('node:16-alpine').inside {sh 'npm install'sh 'npm run build'}}}}stage('Build Docker Image') {steps {script {// 用项目里的 Dockerfile 打镜像def appImage = docker.build("${IMAGE_NAME}:${env.BUILD_ID}")// 也可以顺手 push 到私有仓库,这里先不打 tag 推}}}stage('Deploy') {steps {script {// 停掉旧容器,启动新容器,端口映射用 Nginx 把 dist 包暴露出去sh """docker rm -f ${IMAGE_NAME} || truedocker run -d \--name ${IMAGE_NAME} \-p ${HOST_PORT}:80 \${IMAGE_NAME}:${env.BUILD_ID}"""}}}}
}
关键点拆开说:
- Checkout:直接拉分支,你如果是私有仓库可能得先配好凭据,这一步不赘述。
- Build Frontend:用了
docker.image('node:16-alpine').inside,意思是临时起一个 node 容器,把当前工作目录挂进去运行 npm 命令。这样宿主机不用装 Node 环境,保证构建环境一致性,也不会污染宿主机。构建完 dist 目录留在 workspace 里。 - Build Docker Image:下面要有个 Dockerfile,内容很简单,把 dist 塞进 nginx 镜像就成了。
docker.build自动用当前目录的 Dockerfile 构建,镜像名带上 BUILD_ID 方便区分版本。 - Deploy:直接粗暴地停旧容器然后启动新的。生产环境你可能用 docker-compose 或者更优雅的滚动更新,但个人/小项目这样够用了,停机时间也就一两秒。
Dockerfile 长这样,放在项目根目录:
FROM nginx:alpine
# 把构建好的静态文件复制到 nginx 的 html 目录
COPY dist /usr/share/nginx/html
# 如果你有自定义 nginx 配置,可以挂进去,这里不赘述
EXPOSE 80
关于部署这步,其实直接 docker run 已经能跑,但你可能会想加一个 nginx 配置来处理前端路由 history 模式,或者做反向代理。这种需求可以写个 default.conf 然后在 Dockerfile 里 COPY 进去,或者用 docker-compose 管理配置更灵活。简单版就用上面的 run 命令。
如果项目里已经用了 docker-compose,可以把 deploy stage 改成:
stage('Deploy with Compose') {steps {script {sh '''export IMAGE_TAG=${BUILD_ID}docker-compose down || truedocker-compose up -d'''}}
}
对应的 docker-compose.yml 里镜像标签用变量 ${IMAGE_TAG} 就行。这样回滚也方便,改一下环境变量就切回去了。
整个流水线跑通之后,你每次往 main 分支推代码,Jenkins 自动拉代码、npm 构建、打 Docker 镜像、部署一气呵成。如果怕构建产物把 Jenkins 磁盘撑爆,可以在 pipeline 最后加个清理步骤,把工作区的 node_modules 和中间镜像清理掉。
我前后折腾了两小时搞定,其中一小时在等插件下载…… 搭好之后幸福感提升巨大,再也没手动部署过。现在就算半夜改完代码,打开 Jenkins 界面点一下“立即构建”,安心睡觉,醒来就是新版。
这套方案拓展性也不错,比如加上 stage('Test') 跑单元测试,或者推送镜像到私有仓库再到目标服务器部署。总之基础架子搭好,后面慢慢加料就行。希望这篇能帮你少走点弯路。
