2216 字
6 分钟
Vue SPA GitLab CI/CD 自动部署:Runner 与 1Panel 同机实战

下文以代号 vue-spa 指代该前端仓库(Naive UI、Pinia、Vue Router)。生产环境跑在自托管服务器上,用 1Panel 管理 Nginx 站点,CI/CD 走 GitLab
核心思路是:Runner 和 1Panel 在同一台机器,通过 Docker 卷把站点目录挂进 Job 容器,构建完成后直接把 dist/ 同步过去,无需 SSH、FTP 或对象存储中转。

下文示例中的 <gitlab-host><deploy-host><backend-host><site-name> 等为占位符,请替换为你自己的内网地址或域名;实际项目仓库内仍保留真实配置,博客仅作公开说明时的脱敏版本。

整体架构#

flowchart LR A[开发者 push dev] --> B[GitLab CI build] B --> C[npm ci + npm run build] C --> D[artifacts: dist/] D --> E[deploy job: rsync] E --> F["/deploy-target/dist/"] F --> G["宿主机 1Panel 站点目录"] G --> H[Nginx 对外服务] H --> I["/api/ 反代后端"]

流水线分两个阶段:

  1. build:在 node:22-alpine 镜像里执行 npm cinpm run build,产出 dist/ 并作为 artifact 保留 7 天。
  2. deploy:在 alpine:3.20 里安装 rsync,执行 deploy/deploy.sh,把 artifact 同步到挂载目录。

只有推送到 dev 分支 才会触发(rules: if: $CI_COMMIT_BRANCH == "dev")。

GitLab 与 Runner 前置配置#

流水线能跑起来,需要先准备好 GitLab 实例代码仓库Runner。下面按实际搭建顺序说明(自托管 GitLab + 与 1Panel 同机的 Runner)。

1. 部署 GitLab(自托管)#

常见做法有两种:

  • 1Panel 应用商店:安装 GitLab CE,按面板提示完成初始化,记下访问地址(下文用 http://<gitlab-host> 表示,端口可能是 80443 或自定义如 9091)。
  • Docker 官方镜像:适合已有 Docker 环境的服务器,参考 GitLab Docker 文档 部署。

首次登录会要求设置 root 密码。进入 Admin Area → Overview → Runners 可查看实例级 Runner 概况;项目级 Runner 在各自仓库里配置。

2. 创建项目并推送代码#

  1. GitLab 中 New project → Create blank project,项目名如 vue-spa
  2. 本地关联远程并推送(示例):
git remote add origin http://<gitlab-host>/<group>/vue-spa.git
git push -u origin dev
  1. 确认仓库根目录已有 .gitlab-ci.yml(项目已内置)。
    首次 push 后可在 Build → Pipelines 看到流水线;若尚无 Runner,状态会是 pending(stuck),等 Runner 注册完成即可。

3. 安装 GitLab Runner#

Runner 建议装在 与 1Panel 同一台机器(后面部署要靠 Docker 卷挂载站点目录)。以 Linux + 官方包为例:

# 添加 GitLab 官方源(以 Debian/Ubuntu 为例,其他发行版见官方文档)
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get install gitlab-runner
# 确认 Runner 用户能访问 Docker(executor 选 docker 时需要)
sudo usermod -aG docker gitlab-runner

安装完成后先不要急着改 config.toml,下一步注册时会自动生成基础配置。

4. 注册 Runner#

注册就是把「这台机器上的 Runner 程序」和「GitLab 上的某个 Runner 槽位」绑定起来。

4.1 获取 Registration Token#

GitLab 15+ 逐步改用 Runner authentication token;界面位置如下(任选一种作用域):

作用域路径适用场景
项目级项目 → Settings → CI/CD → Runners → New project runner只跑当前项目,推荐
群组级群组 → Settings → CI/CD → Runners同组多个项目共用
实例级Admin Area → CI/CD → Runners全站共用

点击 New runner / Register a runner,按向导选择:

  • Tags(可选):如 dockerdeploy,在 .gitlab-ci.yml 里用 tags: 指定时可过滤 Runner。
  • Run untagged jobs:若 .gitlab-ci.yml 未写 tags,需勾选 Yes,否则 Job 会一直 pending。
  • Executor:选 docker
  • Default image:可填 node:22-alpine,与 build job 一致。

向导最后会给出 authentication token(或旧版 registration token),以及 gitlab-runner register 的提示。请复制 token,不要提交到仓库

4.2 执行注册命令#

在 Runner 所在服务器上交互式注册(把占位符换成你的实际值):

sudo gitlab-runner register

按提示输入(示例):

GitLab instance URL: http://<gitlab-host>:9091
Registration/Authentication token: <从 GitLab 页面复制的 token>
Description: vue-spa-deploy-runner
Tags: docker,deploy # 可留空
Executor: docker
Default Docker image: node:22-alpine

注册成功后,配置会写入 /etc/gitlab-runner/config.toml,大致如下:

[[runners]]
name = "vue-spa-deploy-runner"
url = "http://<gitlab-host>:9091"
token = "<runner-token>"
executor = "docker"
[runners.docker]
image = "node:22-alpine"
volumes = ["/cache"]

此时还 没有 挂载 1Panel 站点目录,deploy job 即使跑起来也写不进宿主机——下一节补上。

4.3 验证注册#

sudo gitlab-runner verify # 应显示 runner is alive
sudo gitlab-runner list # 列出已注册 runner

回到 GitLab 项目 Settings → CI/CD → Runners,应看到刚注册的 Runner 为 绿色 online。若离线,检查 Runner 服务与 GitLab URL 是否可达:

sudo gitlab-runner restart
sudo systemctl status gitlab-runner

5. 项目 CI/CD 变量(可选)#

Settings → CI/CD → Variables → Add variable 中配置:

KeyValue 示例选项
DEPLOY_URLhttp://<deploy-host>用于 Environment 链接
VITE_DEV_TOKEN你的 JWTProtected + Masked

VITE_DEV_TOKEN 会在 build 阶段被 Vite 打进产物,切勿写入 .gitlab-ci.yml 明文。修改变量后需重新触发流水线。

6. 启用流水线#

  • 确认默认分支或部署分支为 dev(与 rules 一致)。
  • Settings → CI/CD → General pipelines 中 CI/CD 未被禁用。
  • Push 一次代码,在 Build → Pipelines 查看 build → deploy 是否依次成功。

.gitlab-ci.yml 要点#

仓库根目录的 .gitlab-ci.yml 是入口,和项目当前配置大致对应如下:

stages:
- build
- deploy
variables:
NODE_VERSION: "22"
NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.npm"
VITE_API_BASE_URL: "/api"
DEPLOY_PATH: "/deploy-target"
build:
stage: build
image: node:${NODE_VERSION}-alpine
cache:
key: "${CI_COMMIT_REF_SLUG}-npm"
paths:
- .npm/
- node_modules/
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 7 days
rules:
- if: $CI_COMMIT_BRANCH == "dev"
deploy:
stage: deploy
image: alpine:3.20
needs:
- job: build
artifacts: true
before_script:
- apk add --no-cache rsync
script:
- chmod +x deploy/deploy.sh
- ./deploy/deploy.sh
environment:
name: production
url: $DEPLOY_URL
rules:
- if: $CI_COMMIT_BRANCH == "dev"

几个值得留意的细节:

  • VITE_API_BASE_URL: "/api" 在 CI 变量里写死为相对路径,生产环境由 Nginx 把 /api/ 反代到后端,前端代码里不需要写死后端 IP。
  • cache 缓存 .npm/node_modules/,按分支 slug 分 key,重复构建会快不少。
  • needs + artifacts 让 deploy 只等 build 的 dist/,两阶段职责清晰。
  • environment.url: $DEPLOY_URL 在 GitLab 流水线页面可以直接点链接访问部署结果(需在 Variables 里配置)。

同机部署的关键:Runner Docker 卷挂载#

如果 Runner 用 Docker executor,Job 跑在容器里,默认看不到宿主机文件系统
本方案的做法是:把 1Panel 站点根目录挂进容器,映射为 /deploy-target

注册 Runner 后,编辑 /etc/gitlab-runner/config.toml,在对应 [[runners]][runners.docker] 下增加站点卷挂载:

[[runners]]
name = "vue-spa-deploy-runner"
url = "http://<gitlab-host>:9091"
executor = "docker"
[runners.docker]
image = "node:22-alpine"
volumes = ["/cache", "/1Panel/1panel/www/sites/<site-name>/index:/deploy-target:rw"]

保存后重启并验证:

gitlab-runner restart
gitlab-runner verify

含义:

容器内路径宿主机路径用途
/deploy-target/1Panel/1panel/www/sites/<site-name>/index1Panel 站点根目录
/deploy-target/dist/.../index/dist/Nginx root 指向的静态资源

这一步只需配置一次。之后每次 deploy job 写 /deploy-target/dist/,就等于直接更新了线上目录。具体路径以你 1Panel 里站点实际目录为准,可参考项目仓库 deploy/1panel-runner.md

部署脚本:rsync 增量同步#

deploy/deploy.sh 逻辑很直白:

DEPLOY_PATH="${DEPLOY_PATH:-/deploy-target}"
TARGET="${DEPLOY_PATH%/}/dist/"
mkdir -p "${TARGET}"
rsync -av --delete \
--exclude '.DS_Store' \
dist/ "${TARGET}"
  • --delete:删除目标目录里已不存在的旧文件,避免 hash 文件名变更后残留垃圾。
  • DEPLOY_PATH 可通过 CI 变量覆盖,默认 /deploy-target 与 Runner 挂载一致。
  • 若没有 rsync,脚本会回退到 cp -a(CI 里已显式安装 rsync,一般不会走到分支)。

本地想手动验证,可以先 npm run build,再指定目标路径执行同一脚本:

DEPLOY_PATH=/path/to/site/root ./deploy/deploy.sh

Nginx:History 路由 + API 反代#

Vue Router 使用 History 模式时,刷新子路由会 404,需要在 Nginx 里加:

location / {
try_files $uri $uri/ /index.html;
}

API 不走浏览器直连后端(避免跨域、暴露内网地址),统一走 /api/ 前缀由 Nginx 反代:

location /api/ {
proxy_pass http://<backend-host>:12989;
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;
}

开发环境则由 Vite 的 VITE_PROXY_TARGET 做同样的事;生产环境靠 Nginx。
完整示例见项目仓库的 deploy/nginx.conf.example(仓库内为真实地址,博客此处已脱敏)。

环境变量与构建#

DEPLOY_URLVITE_DEV_TOKEN 的配置入口见上文 「项目 CI/CD 变量」

注意:Vite 的环境变量在 npm run build 时写入产物,改 Variables 或 .env.production 后必须重新跑 build,只改 Nginx 或只 redeploy 不会生效。

项目里 VITE_API_BASE_URL 开发与生产均用 /api,后端地址只出现在 Nginx / Vite 代理配置里,前端代码保持环境无关。

和「远程 SSH 部署」的对比#

方式同机卷挂载SSH/rsync 到远程
复杂度Runner 配一次 volumes 即可需 SSH 密钥、known_hosts、防火墙
速度本地磁盘 rsync,极快取决于网络
适用场景GitLab + 1Panel 同机Runner 在别处、站点在远程

同机方案特别适合内网 GitLab + 单台应用服务器的组合:不引入额外中间层,流水线就是「构建 → 拷文件 → Nginx 已有配置直接生效」。

故障排查 checklist#

  1. deploy 成功但页面没更新
    看 Runner 的 volumes 是否挂对路径;宿主机上 .../index/dist/ 时间戳是否变化。

  2. 刷新子路由 404
    检查 Nginx 是否有 try_files ... /index.html

  3. 接口 404 或跨域
    确认 location /api/proxy_pass 指向正确后端;前端 VITE_API_BASE_URL 是否为 /api

  4. 构建通过、环境变量不生效
    Vite 变量必须在 build stage 注入,改 Variables 后重新触发整条流水线。

  5. 流水线一直 pending
    项目 Runners 页是否 online;是否勾选 Run untagged jobs.gitlab-ci.ymltags 是否与 Runner tags 匹配。

  6. gitlab-runner verify 失败
    检查 GitLab URL、authentication token 是否过期、Docker 是否正常运行;必要时在 GitLab 删除旧 Runner 后重新 register。

小结#

这套 GitLab 部署把四件事串在一起:

  1. GitLab + Runner 注册:实例/项目创建 Runner,executor 选 docker,并保证能接 untagged job。
  2. .gitlab-ci.ymldev 分支 push → build artifact → deploy。
  3. Runner 卷挂载:容器内 /deploy-target 对应 1Panel 站点目录,免 SSH。
  4. Nginx:静态资源 + History fallback + /api/ 反代。

你只管合并到 dev,流水线会自动 npm run build 并把 dist/ 推到线上。若同时保留手动 npm run build + 上传的流程,也可以作为 CI 挂掉时的兜底,两套方式产物路径一致即可。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Vue SPA GitLab CI/CD 自动部署:Runner 与 1Panel 同机实战
https://blog.kongdf.com/posts/learning/devops/vue-spa-gitlab-deploy/
作者
孔大夫
发布于
2026-06-03
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时