Gitee Go 多模块 Java 项目自动部署全流程指南从服务器绑定到 Docker Compose 重启一份可落地的实践记录前言手里有一个 RuoYi-Cloud 微服务项目包含网关、认证、系统、文件、定时任务等多个模块。每次改完代码流程都是本地mvn package→ 手动把 jar 包 SCP 到服务器 → SSH 上去docker compose down up。麻烦不说还容易漏传文件。于是决定用 Gitee Go 把这套流程自动化。选它的原因很简单代码在 GiteeRunner 在国内部署到自己的腾讯云服务器全链路零抖动。免费额度对个人项目也够用。这篇文章不讲“Hello World”级别的示例而是完整记录多模块 Maven 项目 Docker Compose 部署到自有服务器的实践过程包括踩过的坑和最终可用的配置。一、整体思路Gitee Go 的部署流程很直观绑定服务器在 Gitee 主机管理里创建主机组安装 Agent构建阶段在 Gitee 的容器里执行mvn package产出 jar 包制品上传把 jar 打包成tar.gz上传到 Gitee 的暂存区部署阶段Agent 把制品下载到目标服务器执行你定义的 Shell 脚本变量控制触发时传入变量控制构建哪些模块、从哪个分支构建核心配置是仓库根目录下的.workflow/里的 YAML 文件Gitee Go 会自动生成一个类似流水线-202609281008.yml的文件。二、绑定服务器先让 Agent 跑起来Gitee Go 的部署不是通过 SSH 推送代码而是通过在目标服务器上安装一个Agent 程序由 Agent 接收流水线下发的指令来执行部署脚本。2.1 创建主机组登录 Gitee进入右上角头像 →设置→Gitee Go→主机管理。创建一个主机组比如命名me-host这个名字后面在流水线 YAML 里要引用。创建时需要选择主机组名称自定义建议有辨识度主机组标识英文标识用于 YAML 引用仓库作用域务必选中你的目标仓库否则流水线无法调用这个主机组2.2 添加主机并安装 Agent在主机组里点击“添加主机”Gitee 会生成一段Agent 安装命令大致长这样bashcurl -k -L https://gitee-agent-shell.gz.bcebos.com/agent/gitee-go-runner-linux-amd64 \ -o gitee-go-runner chmod x gitee-go-runner \ ./gitee-go-runner -u https://server-agent.gitee.com/gitee_sa_server \ -t labelIdxxxtokenxxxsignxxxtimestampxxx \ manager -g --first-install -c 10把这段命令复制到目标服务器上执行。执行后会看到类似输出textagent registered successfully with uuid: 893bb0e8-4b9a-4299-a16c-41bde86289e1 Agent started看到Agent started就说明安装成功。回到 Gitee 主机管理页面刷新会看到这台服务器状态变为“在线”勾选并添加即可。⚠️两个关键细节Agent 进程需要保持运行。建议用nohup或 systemd 托管否则 SSH 断开后进程可能退出。安装日志里会生成一个UUID如893bb0e8-...这个 UUID 在流水线 YAML 的hostID字段里要引用先记下来。2.3 在流水线中绑定主机在deployagent任务中通过hostGroupID字段绑定yaml- step: deployagent hostGroupID: ID: me-host # 主机组标识 hostID: - # Agent 的 UUIDhostID可以填多个支持多主机部署。三、准备工作先让应用在服务器上手动跑通这是最重要的一步没有之一。很多人在这一步跳过结果部署脚本写完流水线跑通了但应用起不来分不清是 CI 的问题还是应用的问题。正确顺序是在服务器上手动把 jar 传到正确目录用 Docker Compose 手动启动所有容器确认docker compose ps显示所有服务Up确认应用能正常访问只有手动流程跑通了才值得把它写进部署脚本。踩坑提示java -jar启动时JVM 参数必须放在-jar前面。java -jar -Xmx512M app.jar会被 Java 当成在运行一个叫-Xmx512M的文件报错Unable to access jarfile。正确写法是java -Xmx512M -Xms256M -jar app.jar。四、构建阶段多模块项目的配置4.1 指定 JDK 和 Maven 版本在buildmaven任务中必须在 step 的直属属性里指定jdkVersion和mavenVersion不能嵌套在inputs下yaml- step: buildmaven jdkVersion: 17 mavenVersion: 3.9.8 commands: - mvn clean package -Dmaven.test.skiptrue -U -B踩坑记录我一开始放了两个buildmaven步骤第一个没指定 JDK 版本结果用了默认的 JDK 8 编译报invalid target release: 17。只保留一个构建步骤把 JDK 版本写对。4.2 制品收集让 Gitee Go 找到你的 jarartifacts.path指定的是相对于仓库根目录的路径。对于 RuoYi 这种多模块项目jar 分散在各模块的target/或自定义输出目录下。最终可用的配置yamlartifacts: - name: BUILD_ARTIFACT path: - ./pipeline-artifacts type: .tar.gz配合构建命令里的收集逻辑bashrm -rf ./pipeline-artifacts mkdir -p ./pipeline-artifacts find . -name *.jar -type f ! -name *sources* ! -name *javadoc* ! -name *.jar.original -exec cp {} ./pipeline-artifacts/ \; find ./pipeline-artifacts -maxdepth 1 -type f -name *.jar -print先加一句find确认 jar 的实际位置再配置artifacts.path。盲目猜测路径会导致Error: dont exist xxx.jar。4.3 按模块构建用变量控制如果想通过流水线变量控制构建哪些模块可以用 Maven 的-pl和-am参数yamlvariables: global: - BUILD_MODULE - BRANCH steps: - step: buildmaven jdkVersion: 17 mavenVersion: 3.9.8 commands: - |- SELECTED_BRANCH${BRANCH:-master} case ${SELECTED_BRANCH} in master|dev) ;; *) echo 不支持的分支${SELECTED_BRANCH}; exit 1 ;; esac SELECTED_MODULES$(printf %s ${BUILD_MODULE:-all} | tr -d [] | tr ; ,) echo 构建分支${SELECTED_BRANCH} echo 构建模块${SELECTED_MODULES} if [ -z ${SELECTED_MODULES} ] || [ ${SELECTED_MODULES} all ] || printf %s ,${SELECTED_MODULES}, | grep -q ,all,; then mvn clean package -Dmaven.test.skiptrue -U -B else mvn clean package -pl ${SELECTED_MODULES} -am -Dmaven.test.skiptrue -U -B fi触发流水线时填入ruoyi-auth,ruoyi-gateway就只构建这两个模块及其依赖。不填时默认all全量构建。变量引用语法${BRANCH:-master}表示如果BRANCH未定义或为空就用master兜底。4.4 敏感信息用环境变量管理如果需要在流水线中使用密码、密钥等敏感信息不要直接写在 YAML 里。Gitee Go 提供了环境变量管理功能在仓库「管理」→「环境变量管理」中添加。添加后在流水线中用$变量名引用yamlcommands: - echo $APP_SECRET环境变量只在流水线配置文件中生效对用户自定义的其他脚本文件不起作用。五、部署阶段主机部署脚本5.1 制品下载配置Gitee Go 会把制品下载到~/gitee_go/deploy/output.tar.gz配置如下yaml- step: deployagent displayName: 主机部署 deployArtifact: - name: output target: ~/gitee_go/deploy source: build dependArtifact: BUILD_ARTIFACT artifactRepository: default artifactName: output artifactVersion: latest5.2 部署脚本核心逻辑这是整个流程中最关键的部分。脚本需要解压制品把 jar 分发到正确的目录备份旧 jar保留最近 N 个停止旧容器用 Docker Compose 重启清理临时目录以下是我最终可用的脚本注意目标服务器的/bin/sh可能是 dash不能用 bash 的关联数组语法bash#!/bin/bash set -eu DEPLOY_DIR/opt/dailai ARTIFACT_DIR${HOME}/gitee_go/deploy TIMESTAMP$(date %Y%m%d%H%M%S) MAX_BACKUPS10 mkdir -p ${ARTIFACT_DIR} ${DEPLOY_DIR} # 用空格分隔的列表避免 bash 关联数组dash 不支持 JARSruoyi-auth.jar ruoyi-gateway.jar ruoyi-modules-system.jar ruoyi-modules-gen.jar ruoyi-modules-job.jar ruoyi-modules-file.jar ruoyi-visual-monitor.jar DIRS/opt/dailai/auth/jar /opt/dailai/gateway/jar /opt/dailai/modules/system/jar /opt/dailai/modules/gen/jar /opt/dailai/modules/job/jar /opt/dailai/modules/file/jar /opt/dailai/visual/monitor/jar CONTAINERSruoyi-auth ruoyi-gateway ruoyi-modules-system ruoyi-modules-gen ruoyi-modules-job ruoyi-modules-file ruoyi-visual-monitor cd ${ARTIFACT_DIR} tar zxvf output.tar.gz INDEX0 for JAR in ${JARS}; do TARGET_DIR$(echo ${DIRS} | cut -d -f$((INDEX 1))) CONTAINER$(echo ${CONTAINERS} | cut -d -f$((INDEX 1))) TARGET_FILE${TARGET_DIR}/${JAR} SOURCE_FILE$(find . -type f -name ${JAR} -print -quit) if [ -z ${SOURCE_FILE} ]; then echo 跳过未构建模块${JAR} INDEX$((INDEX 1)) continue fi mkdir -p ${TARGET_DIR} if [ -f ${TARGET_FILE} ]; then docker stop ${CONTAINER} 2/dev/null || true mv ${TARGET_FILE} ${TARGET_FILE}.${TIMESTAMP} fi cp ${SOURCE_FILE} ${TARGET_FILE} # 清理历史备份保留最近 MAX_BACKUPS 个 cd ${TARGET_DIR} ls -1t ${JAR}.* 2/dev/null | tail -n $((MAX_BACKUPS 1)) | while read -r OLD; do rm -f ${OLD} done cd ${ARTIFACT_DIR} INDEX$((INDEX 1)) done cd ${DEPLOY_DIR} docker compose down docker compose up -d --build docker compose ps rm -rf ${ARTIFACT_DIR}/*5.3 踩坑记录踩坑 1Shell 解释器不是 bash。目标服务器的/bin/sh链接到dash而dash不支持declare -A关联数组。报错Syntax error: ( unexpected。解决方案是改用普通字符串列表 cut提取。踩坑 2Docker Compose 的context路径要对。如果 compose 文件在/opt/docker-compose.ymlcontext: ./dailai/gateway会指向/opt/dailai/gateway。如果 compose 文件在/opt/dailai/则要写context: ./gateway。踩坑 3基础镜像可能已经不存在。openjdk:17这个 tag 在 Docker Hub 上已经废弃拉取会报not found。改用eclipse-temurin:17-jdk或amazoncorretto:17。踩坑 4Docker 镜像拉取网络问题。国内服务器拉取 Docker Hub 镜像可能超时或 403。如果配置了代理不要同时配置加速器否则 Docker 会优先走代理去访问加速器链路冲突。二选一即可。六、完整流水线结构把绑定服务器、变量配置、构建阶段、部署阶段全部串起来yamlversion: 1.0 name: 构建、部署到自有主机 displayName: 构建、部署到自有主机 variables: global: - BUILD_MODULE - BRANCH stages: - name: 构建 displayName: 构建 strategy: naturally trigger: auto steps: - step: buildmaven name: build_maven displayName: Maven 构建 jdkVersion: 17 mavenVersion: 3.9.8 commands: - |- set -eu SELECTED_BRANCH${BRANCH:-master} case ${SELECTED_BRANCH} in master|dev) ;; *) echo 不支持的分支${SELECTED_BRANCH}; exit 1 ;; esac SELECTED_MODULES$(printf %s ${BUILD_MODULE:-all} | tr -d [] | tr ; ,) echo 构建分支${SELECTED_BRANCH} echo 构建模块${SELECTED_MODULES} if [ -z ${SELECTED_MODULES} ] || [ ${SELECTED_MODULES} all ] || printf %s ,${SELECTED_MODULES}, | grep -q ,all,; then mvn clean package -Dmaven.test.skiptrue -U -B else mvn clean package -pl ${SELECTED_MODULES} -am -Dmaven.test.skiptrue -U -B fi rm -rf ./pipeline-artifacts mkdir -p ./pipeline-artifacts find . -name *.jar -type f ! -name *sources* ! -name *javadoc* ! -name *.jar.original -exec cp {} ./pipeline-artifacts/ \; echo 本次构建 JAR find ./pipeline-artifacts -maxdepth 1 -type f -name *.jar -print artifacts: - name: BUILD_ARTIFACT path: - ./pipeline-artifacts type: .tar.gz settings: [] caches: - ~/.m2 strategy: retry: 0 timeout: 4 resource: cpu: 2 memory: 4 - name: 部署 displayName: 部署 strategy: naturally trigger: auto steps: - step: deployagent name: _1 displayName: 主机部署 deployArtifact: - name: output target: ~/gitee_go/deploy source: build dependArtifact: BUILD_ARTIFACT artifactRepository: default artifactName: output artifactVersion: latest script: |- #!/bin/bash set -eu DEPLOY_DIR/opt/dailai ARTIFACT_DIR${HOME}/gitee_go/deploy TIMESTAMP$(date %Y%m%d%H%M%S) MAX_BACKUPS10 mkdir -p ${ARTIFACT_DIR} ${DEPLOY_DIR} JARSruoyi-auth.jar ruoyi-gateway.jar ruoyi-modules-system.jar ruoyi-modules-gen.jar ruoyi-modules-job.jar ruoyi-modules-file.jar ruoyi-visual-monitor.jar DIRS/opt/dailai/auth/jar /opt/dailai/gateway/jar /opt/dailai/modules/system/jar /opt/dailai/modules/gen/jar /opt/dailai/modules/job/jar /opt/dailai/modules/file/jar /opt/dailai/visual/monitor/jar CONTAINERSruoyi-auth ruoyi-gateway ruoyi-modules-system ruoyi-modules-gen ruoyi-modules-job ruoyi-modules-file ruoyi-visual-monitor cd ${ARTIFACT_DIR} tar zxvf output.tar.gz INDEX0 for JAR in ${JARS}; do TARGET_DIR$(echo ${DIRS} | cut -d -f$((INDEX 1))) CONTAINER$(echo ${CONTAINERS} | cut -d -f$((INDEX 1))) TARGET_FILE${TARGET_DIR}/${JAR} SOURCE_FILE$(find . -type f -name ${JAR} -print -quit) if [ -z ${SOURCE_FILE} ]; then echo 跳过未构建模块${JAR} INDEX$((INDEX 1)) continue fi mkdir -p ${TARGET_DIR} if [ -f ${TARGET_FILE} ]; then docker stop ${CONTAINER} 2/dev/null || true mv ${TARGET_FILE} ${TARGET_FILE}.${TIMESTAMP} fi cp ${SOURCE_FILE} ${TARGET_FILE} cd ${TARGET_DIR} ls -1t ${JAR}.* 2/dev/null | tail -n $((MAX_BACKUPS 1)) | while read -r OLD; do rm -f ${OLD} done cd ${ARTIFACT_DIR} INDEX$((INDEX 1)) done cd ${DEPLOY_DIR} docker compose down docker compose up -d --build docker compose ps rm -rf ${ARTIFACT_DIR}/* hostGroupID: ID: me-host hostID: - 893bb0e8-4b9a-4299-a16c-41bde86289e1 notify: [] strategy: retry: 0 timeout: 5 triggers: trigger: manual notify: [] strategy: blocking: true七、触发方式手动还是自动你目前配置的是trigger: manual需要手动点击运行。Gitee Go 还支持Push 触发代码提交后自动运行yamltriggers: push: branches: include: - master - dev或者通过提交信息关键字触发yamltriggers: push: commitMessage: ^build:这样只有 commit message 以build:开头的提交才会触发流水线避免每次提交都跑一遍。八、小结环节关键操作常见坑绑定服务器创建主机组 → 安装 Agent → 获取 UUIDAgent 进程要托管UUID 要记录定义变量variables.global声明触发时传入变量为空要用:-默认值兜底敏感信息环境变量管理用$VAR引用不要写死在 YAML 里构建阶段指定 JDK/Maven 版本收集 jar两个构建步骤会覆盖路径要确认部署阶段解压、分发、备份、重启dash 不支持关联数组Compose context 要对触发方式manual手动 /push自动Push 触发要配分支或 commit 匹配先确保服务器 Agent 在线、主机组正确关联仓库再配置变量和触发方式流水线才能真正跑通。一旦跑通后续每次 push 代码流水线自动构建、部署、重启容器真正做到了“只关注开发”。这套配置也适用于其他多模块 Java 项目 Docker Compose 部署的场景只需要调整模块名、路径和容器名即可。