下文以代号 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>等为占位符,请替换为你自己的内网地址或域名;实际项目仓库内仍保留真实配置,博客仅作公开说明时的脱敏版本。
整体架构
流水线分两个阶段:
- build:在
node:22-alpine镜像里执行npm ci和npm run build,产出dist/并作为 artifact 保留 7 天。 - 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>表示,端口可能是80、443或自定义如9091)。 - Docker 官方镜像:适合已有 Docker 环境的服务器,参考 GitLab Docker 文档 部署。
首次登录会要求设置 root 密码。进入 Admin Area → Overview → Runners 可查看实例级 Runner 概况;项目级 Runner 在各自仓库里配置。
2. 创建项目并推送代码
- GitLab 中 New project → Create blank project,项目名如
vue-spa。 - 本地关联远程并推送(示例):
git remote add origin http://<gitlab-host>/<group>/vue-spa.gitgit push -u origin dev- 确认仓库根目录已有
.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 bashsudo 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(可选):如
docker、deploy,在.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>:9091Registration/Authentication token: <从 GitLab 页面复制的 token>Description: vue-spa-deploy-runnerTags: docker,deploy # 可留空Executor: dockerDefault 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 alivesudo gitlab-runner list # 列出已注册 runner回到 GitLab 项目 Settings → CI/CD → Runners,应看到刚注册的 Runner 为 绿色 online。若离线,检查 Runner 服务与 GitLab URL 是否可达:
sudo gitlab-runner restartsudo systemctl status gitlab-runner5. 项目 CI/CD 变量(可选)
在 Settings → CI/CD → Variables → Add variable 中配置:
| Key | Value 示例 | 选项 |
|---|---|---|
DEPLOY_URL | http://<deploy-host> | 用于 Environment 链接 |
VITE_DEV_TOKEN | 你的 JWT | Protected + 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 restartgitlab-runner verify含义:
| 容器内路径 | 宿主机路径 | 用途 |
|---|---|---|
/deploy-target | /1Panel/1panel/www/sites/<site-name>/index | 1Panel 站点根目录 |
/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.shNginx: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_URL、VITE_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
-
deploy 成功但页面没更新
看 Runner 的volumes是否挂对路径;宿主机上.../index/dist/时间戳是否变化。 -
刷新子路由 404
检查 Nginx 是否有try_files ... /index.html。 -
接口 404 或跨域
确认location /api/的proxy_pass指向正确后端;前端VITE_API_BASE_URL是否为/api。 -
构建通过、环境变量不生效
Vite 变量必须在 build stage 注入,改 Variables 后重新触发整条流水线。 -
流水线一直 pending
项目 Runners 页是否 online;是否勾选 Run untagged jobs;.gitlab-ci.yml的tags是否与 Runner tags 匹配。 -
gitlab-runner verify失败
检查 GitLab URL、authentication token 是否过期、Docker 是否正常运行;必要时在 GitLab 删除旧 Runner 后重新 register。
小结
这套 GitLab 部署把四件事串在一起:
- GitLab + Runner 注册:实例/项目创建 Runner,executor 选 docker,并保证能接 untagged job。
.gitlab-ci.yml:dev分支 push → build artifact → deploy。- Runner 卷挂载:容器内
/deploy-target对应 1Panel 站点目录,免 SSH。 - Nginx:静态资源 + History fallback +
/api/反代。
你只管合并到 dev,流水线会自动 npm run build 并把 dist/ 推到线上。若同时保留手动 npm run build + 上传的流程,也可以作为 CI 挂掉时的兜底,两套方式产物路径一致即可。
部分信息可能已经过时