Vue3 项目 GitLab CI/CD 自动构建并推送到 Harbor 教程

在前端项目交付中,如何保证构建一致性、镜像可追溯性、自动化交付,是团队必须解决的问题。参考 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-fromBUILDKIT_INLINE_CACHE 提升构建速度
  • 环境变量注入VUE_APP_ENV=test/production 控制前端构建环境
  • 安全性:Harbor 凭据通过 CI/CD 变量注入,避免明文泄露
  • 清理策略after_script 清理 Runner 本地镜像,防止磁盘膨胀

五、总结

通过本文,你可以实现:

  1. Vue3 项目容器化(Dockerfile 构建静态资源 + Nginx 托管)
  2. GitLab CI/CD 自动化(分支/标签策略、缓存加速、推送 Harbor)
  3. 可追溯与可维护(镜像标签规范、构建信息存档、日志清理)

最终达到 代码提交 → 自动构建 → 镜像推送 → 部署上线 的完整闭环。

相关推荐
happy_0x3f17 小时前
Claude Code 离线安装方案揭秘
ci/cd
杨了个杨89821 天前
GitLab的简介及安装
gitlab
AOwhisky2 天前
云原生 DevOps 工具链从入门到实战(第一期)——DevOps概述与GitLab部署——从理念到工具落地
运维·ci/cd·云原生·gitlab·开发·devops
2301_773643622 天前
1.SDLC 与模型(简述)
ci/cd
lbb 小魔仙3 天前
Git + Python 项目工作流最佳实践:pre-commit、CI、CHANGELOG 自动化
git·python·ci/cd
不厌 -_-3 天前
gitlab中怎么创建MR提交模板
gitlab·mr
MC皮蛋侠客4 天前
uv 系列(七):CI/CD、Docker 与私有索引——生产级交付
python·ci/cd·docker·uv
diving deep4 天前
CI/CD
ci/cd
酿情师4 天前
Gitlab 任意文件读取漏洞(CVE-2016-9086)
gitlab
lxw18449125144 天前
PHP后端(CI框架方向)面试题库,分为基础必问、MySQL、Redis、工程运维、高阶加分、场景实操六大模块,适配该JD全部考点,附带标准答案
ci/cd·面试·php