把 Java 项目从手动 SCP 升级到 Gitee Go 自动部署:一份踩坑实录
手里有一个跑在腾讯云轻量服务器上的小程序后端(Spring Boot,体重记录类的小项目),之前一直是本地改完代码 → 打包 → scp 上传 → SSH 上去 kill 老进程 → java -jar 起新进程。麻烦倒不算麻烦,主要是每次都觉得「这流程明明可以全自动的,为什么要手动搞」。
于是花了大概一个周末,把这套流程用 Gitee Go 跑通了。从开始折腾到生产可用,中间踩了不下六个坑,每一个都值得单独拎出来讲一讲。这篇不是教程,是踩坑日志------如果你也在走这条路,希望少走几步弯路。
选型:为什么是 Gitee Go 不是 GitHub Actions
一开始想用 GitHub Actions,毕竟最熟悉。但服务器在国内,拉 GitHub 的代码、跑 runner 都有不稳定感。最后选了 Gitee Go,三个原因:
- 国内访问快。代码在 Gitee,runner 在 Gitee,部署到腾讯云 CVM------全链路都在国内,零抖动。
- 免费额度够用。个人项目每月 500 分钟 + 单仓库 200 分钟永久免费,比 Actions 的 2000 分钟少,但单机部署一次构建也就 1 分钟。
- 自带主机部署插件 。
deploy@agent装一台 Agent 到目标服务器,就能直接 deploy,不用再单独搞 SSH 公私钥那套。
唯一坑爹的地方:官方文档是过时的。比如「服务 → Gitee Go」的入口早就改名了。后面我会逐个说哪些坑是文档锅、哪些是真的设计问题。
顺带一提,Gitee 顶部导航里现在已经没有「服务」了,叫「流水线 」(在 代码 / Issues / Pull Requests / Wiki / 统计 / 流水线 / 服务 / 管理 这一排里)。而且你在 GUI 里建好的流水线,会生成一个形如 流水线-202609041137.yml 的文件被提交到仓库------这名字一看就是自动生成的,后面讲「流水线被 push 冲掉」那坑就跟它有关。
第一坑:先别急着配流水线,先把 systemd 跑通
很多人(包括之前的我)一上来就建流水线、配脚本,结果部署到一半发现应用根本起不来,然后分不清是流水线的问题还是代码的问题。
正确顺序:先把应用在目标服务器上用 systemd 手动跑通,再接 CI/CD。
ExecStart 里有个非常容易踩的坑:
java -jar -Xmx512M -Xms256M xxx.jar ← 错
java -Xmx512M -Xms256M -jar xxx.jar ← 对
JVM 参数必须放在 -jar 前面 ,否则 Java 会把 -Xmx 当成 jar 文件名。报错长这样:
my-app.service: Main process exited, code=exited, status=1/FAILURE
我当时看到 status=1/FAILURE 以为是 JDK 路径错了,折腾了半天才反应过来是参数顺序。systemd 对参数顺序的容忍度是零,错一个字符就拒绝启动。
SuccessExitStatus=143 这行也很关键------systemctl stop 发 SIGTERM 给 Java 进程,Java 会返回 143(128+15),不加这行 systemd 会把它当成「异常退出」然后立刻重启,导致 systemctl stop 永远停不下来。
第二坑:Maven 找不到 POM
代码结构是这样的:
my-app/
└── backend/
└── pom.xml
不是单模块项目,所以仓库根目录里没有 pom.xml。Gitee Go 默认 Maven 任务的工作目录是仓库根目录,所以一上来就报错:
[ERROR] The goal you specified requires a project to execute but there is no POM in this directory
修法:在构建命令前加一行 cd backend。小事,但定位起来要看日志才知道是路径问题。
第三坑:制品名带点号被拒
构建成功了,制品上传这一步炸了:
key "my-app-1.2.3" is invalid: character '.' at position 16 is not allowed
我用了 my-app-1.2.3 作为制品名(项目名+版本号),但 Gitee Go 的制品命名规则只允许字母、数字、下划线、连字符,点号不行。
去掉版本号,改成 my-app,构建阶段就完全绿了。
第四坑------也是最离谱的一个:制品名带连字符
构建成功了,制品下载的时候炸了,错误长这样:
+ curl -kL -o /root/gitee_go/deploy/my-app.tar.gz app
curl: (6) Could not resolve host: app
我盯了半小时才反应过来------app 这个诡异的「下载地址」,其实是 agent 自动生成的下载命令被 bash 截断了。
真相是这样的:Gitee Go 的主机部署插件会自动跑一条 curl 把制品下载到目标主机,命令大概长这样:
bash
curl -kL -o /root/gitee_go/deploy/${ARTIFACT_NAME}.tar.gz "${URL}"
问题就出在 ${ARTIFACT_NAME}。我的制品名是 my-app。在 bash 里,${var-default} 是「变量未设时用默认值」的语法------my-app 这个名字被解析成了「取变量 my 的值,找不到就用默认值 -app」,于是下载命令就变成了:
curl ... /root/gitee_go/deploy/-app.tar.gz "${URL}"
不对,连字符后面的内容被吃掉了,最终就成了 app。
这个 bug 的铁证是:每次把制品名改成什么,curl 那个莫名其妙的地址就精准地丢掉 - 前面的部分 。my-app-1.2.3 → 假 URL 是 app-1.2.3;my-app → 假 URL 是 app。精度到让我怀疑 Gitee 团队是不是故意的。
绕开的办法:制品名只用字母、数字、下划线 。我后来改成 myapp,立刻就过了。官方示例也都老老实实用 output、BUILD_ARTIFACT 这类名字,不是没道理的。
这个坑踩了我一个多小时。后来想明白是 bash 变量语法之后觉得又好气又好笑------估计 Gitee 团队从来没遇到过有人用带连字符的制品名,所以没考虑到这个边界情况。
第五坑:部署脚本里不该有的 curl
制品下载成功后,我的部署脚本一开始是这样的(参考的官方示例):
bash
curl -kL -o /opt/my-app/my-app.tar.gz app
bash /opt/my-app/deploy.sh
看起来很合理对吧?但 agent 在执行脚本之前,已经把制品下载到 /root/gitee_go/deploy/ 了。我在脚本里又自己 curl 一次,等于重复下载,还引用了一个不存在的「制品名」当 URL。
正确的脚本应该是:
bash
set -e
ls -la /root/gitee_go/deploy/ # 看看 agent 给我们下载了什么
rm -rf /tmp/wt-deploy && mkdir -p /tmp/wt-deploy
tar zxvf /root/gitee_go/deploy/myapp.tar.gz -C /tmp/wt-deploy
JAR=$(find /tmp/wt-deploy -name 'my-app-*.jar' ! -name '*.original' | head -1)
echo "找到 jar: $JAR"
[ -f /opt/my-app/my-app.jar ] && cp /opt/my-app/my-app.jar /opt/my-app/my-app.jar.bak
cp "$JAR" /opt/my-app/my-app.jar
systemctl restart my-app
sleep 5
systemctl status my-app --no-pager
整个脚本永远不要再写 curl------agent 已经帮你下好了,你只管用。
第六坑:流水线被 push 冲掉,差点前功尽弃
最让我哭笑不得的一坑。某次改了点东西 git push 之后,发现流水线又跑不动了。打开一看,我之前在 GUI 里精心调整的部署脚本、制品名、主机组 ID,全部被打回了最初的版本。
原因后来搞清楚了:Gitee Go 的「流水线」本质就是仓库里 .workflow/*.yml 这个文件的镜像 。你在 GUI 里改完保存,它会自动 commit 一个 yaml 到仓库的 .workflow/ 目录;反过来,你 push 代码时如果这个目录里有内容(哪怕是别的文件),仓库版本就会覆盖 GUI 状态。
我当时在 GUI 改了之后没有把这个 yaml 拉回本地,下次 push 一个不同状态的 .workflow/ 就把我覆盖了。
修法是两步:
- 重新用 GUI 编辑器把流水线配回去(或者用代码视图粘贴一段完整的 yaml)
- 把
.workflow/加进.gitignore:
bash
echo '.workflow/' >> .gitignore
git add .gitignore
git commit -m "chore: 不再跟踪 .workflow 目录"
git push
从此以后流水线归 GUI 管,代码 push 不会再影响它。
现在的日常
本地 IDE 改代码
↓ git push
Gitee Go 自动触发 Maven 构建(40 秒)
↓
Agent 把制品下载到 /root/gitee_go/deploy/
↓ 主机部署任务
解压 → 替换 jar → systemctl restart → 状态校验
↓ 10 秒后
新版本已在生产跑
改完代码 push 一下,喝口水回来就是新版本。从最初手动 scp + kill + 启动的 5 分钟,到现在全流程 1 分钟不到。
下面是这条流水线真实跑通的一次截图(构建 49 秒 + 部署 15 秒,一共 1 分 13 秒,全绿):

注意左边那栏的信息------「触发方式:手动触发」是因为我还没把它接到推送上(这条流水线是我手动点的「执行」);右边两个「成功」分别是 Maven 构建(49s) 和 主机部署(15s)。等我把触发方式改成 push 自动触发,连这个手动点都不用了。
给同样要走这条路的人几句忠告
- 先跑通 systemd 再配流水线------别让两个问题混在一起排查
- 制品名不要带连字符 ------这玩意会被 bash 当
${var-default}截断(这个 bug 应该写在文档最显眼的位置,但 Gitee 没写) .workflow/加进.gitignore------否则每次 push 都是和流水线赌命
希望这篇能帮你少踩几个。折腾完你会发现,其实没那么复杂,只是细节多。
附,项目小程序码,欢迎体验指正
