在前端项目交付中,如何保证构建一致性、镜像可追溯性、自动化交付,是团队必须解决的问题。参考 Spring Boot 项目 GitLab CI/CD 自动构建并推送到 Harbor 教程,本文将以 Vue3 项目为例,演示如何通过 GitLab CI/CD 自动构建并推送镜像到 Harbor。
一、环境准备
- GitLab:14+,自建或 GitLab.com
- GitLab Runner:Docker executor,Runner 节点需安装 Docker
- Harbor :2.5+,已创建项目(如
ayy-admin) - CI/CD 变量配置 :
HARBOR_USER:Harbor 用户名(推荐使用 Robot 账号)HARBOR_PASS:Harbor 密码或 token- (可选)
SSH_PRIVATE_KEY:若需要远程部署
二、Dockerfile 示例
Vue3 项目通常需要先构建静态资源,再由 Nginx 提供服务:
dockerfile
# -------------------------------
# 构建阶段:使用 Node 构建 Vue3 前端
# -------------------------------
FROM node:20-alpine AS build
WORKDIR /app
# 设置 npm 镜像源和 Node.js 内存限制(提前设置)
RUN npm config set registry https://registry.npmmirror.com/
ENV NODE_OPTIONS="--max-old-space-size=4096"
# 先复制依赖文件,利用 Docker 缓存(重要优化)
COPY package*.json ./
# 安装依赖
RUN npm install
# 再复制源码(不包括 node_modules,通过 .dockerignore 排除)
COPY . .
# 构建
RUN npm run build
# -------------------------------
# 运行阶段:使用 Nginx 提供静态资源
# -------------------------------
FROM nginx:1.28.0-alpine
WORKDIR /usr/share/nginx/html
RUN rm -rf ./*
COPY --from=build /app/dist/ ./
COPY deploy/nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget -qO- http://localhost:8080/ || exit 1
CMD ["nginx", "-g", "daemon off;"]
三、GitLab CI/CD 配置
以下 .gitlab-ci.yml 实现了 main 分支(测试环境) 与 tag 发布(生产环境) 的自动构建与推送。
yaml
stages:
- build
- push
variables:
DOCKER_HOST: unix:///var/run/docker.sock
REGISTRY: 192.168.0.12:5080
PROJECT: ayy-admin
IMAGE_NAME: ayy-admin-vue3
# 禁用 Git LFS
GIT_LFS_SKIP_SMUDGE: "1"
# ==================== 锚点定义 ====================
.docker-base: &docker-base
image: docker:20.10.24
before_script:
# 验证 Docker 可用(使用宿主机 Docker)
- echo "使用宿主机 Docker"
- docker info
- docker version
# 登录 Harbor
- echo "$HARBOR_PASS" | docker login -u "$HARBOR_USER" --password-stdin $REGISTRY
.ssh-base: &ssh-base
image: alpine:latest
before_script:
- apk add --no-cache openssh-client curl
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# ==================== 测试环境 ====================
build-test:
<<: *docker-base
stage: build
script:
- export IMAGE_TAG="test-${CI_COMMIT_SHORT_SHA}"
- export CACHE_IMAGE="$REGISTRY/$PROJECT/$IMAGE_NAME:test-cache"
- echo "开始构建测试镜像 $IMAGE_TAG"
# 尝试拉取缓存镜像
- docker pull $CACHE_IMAGE || echo "缓存镜像不存在,将从头构建"
# 构建镜像
- |
docker build \
--cache-from $CACHE_IMAGE \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--build-arg NODE_ENV=production \
--build-arg VUE_APP_ENV=test \
--build-arg BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg GIT_COMMIT=$CI_COMMIT_SHORT_SHA \
-t $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG \
-t $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest \
-t $CACHE_IMAGE \
.
- echo "✓ 镜像构建成功"
- docker images | grep $IMAGE_NAME
# 保存构建信息
- echo "IMAGE_TAG=$IMAGE_TAG" > build.env
- echo "ENVIRONMENT=test" >> build.env
artifacts:
reports:
dotenv: build.env
expire_in: 1 day
only:
- main
tags:
- frontend
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
push-test:
<<: *docker-base
stage: push
dependencies:
- build-test
script:
- echo "开始推送测试镜像 $IMAGE_TAG"
- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG
- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest
- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:test-cache
- echo "✓ 镜像推送成功"
# 显示推送的镜像信息
- echo "推送的镜像:"
- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG"
- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest"
after_script:
# 清理本地镜像
- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG || true
- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest || true
- docker system prune -f || true
only:
- main
tags:
- frontend
retry:
max: 2
when:
- runner_system_failure
# ==================== 生产环境 ====================
build-prod:
<<: *docker-base
stage: build
script:
- export IMAGE_TAG="${CI_COMMIT_TAG}"
- export CACHE_IMAGE="$REGISTRY/$PROJECT/$IMAGE_NAME:prod-cache"
- echo "开始构建生产镜像 $IMAGE_TAG"
# 验证标签格式 (v1.0.0)
- |
if ! echo "$IMAGE_TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "❌ 错误: 标签格式必须为 vX.Y.Z (例如: v1.0.0)"
echo "当前标签: $IMAGE_TAG"
exit 1
fi
- echo "✓ 标签格式验证通过 $IMAGE_TAG"
# 尝试拉取缓存镜像
- docker pull $CACHE_IMAGE || echo "缓存镜像不存在,将从头构建"
# 构建镜像
- |
docker build \
--cache-from $CACHE_IMAGE \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--build-arg NODE_ENV=production \
--build-arg VUE_APP_ENV=production \
--build-arg BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg GIT_TAG=$CI_COMMIT_TAG \
-t $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG \
-t $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest \
-t $CACHE_IMAGE \
.
- echo "✓ 镜像构建成功"
- docker images | grep $IMAGE_NAME
# 保存构建信息
- echo "IMAGE_TAG=$IMAGE_TAG" > build.env
- echo "ENVIRONMENT=production" >> build.env
artifacts:
reports:
dotenv: build.env
expire_in: 30 days
only:
- tags
tags:
- frontend
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
push-prod:
<<: *docker-base
stage: push
dependencies:
- build-prod
script:
- echo "开始推送生产镜像 $IMAGE_TAG"
- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG
- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest
- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:prod-cache
- echo "✓ 镜像推送成功"
# 显示推送的镜像信息
- echo "推送的镜像:"
- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG"
- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest"
after_script:
# 清理本地镜像
- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG || true
- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest || true
- docker system prune -f || true
only:
- tags
tags:
- frontend
retry:
max: 2
when:
- runner_system_failure
四、关键点解析
- 分支策略 :
main→ 测试镜像;tag→ 生产镜像 - 缓存加速 :利用
--cache-from与BUILDKIT_INLINE_CACHE提升构建速度 - 环境变量注入 :
VUE_APP_ENV=test/production控制前端构建环境 - 安全性:Harbor 凭据通过 CI/CD 变量注入,避免明文泄露
- 清理策略 :
after_script清理 Runner 本地镜像,防止磁盘膨胀
五、总结
通过本文,你可以实现:
- Vue3 项目容器化(Dockerfile 构建静态资源 + Nginx 托管)
- GitLab CI/CD 自动化(分支/标签策略、缓存加速、推送 Harbor)
- 可追溯与可维护(镜像标签规范、构建信息存档、日志清理)
最终达到 代码提交 → 自动构建 → 镜像推送 → 部署上线 的完整闭环。