
Mac/Linux 使用 Docker Jenkins 自动部署 Spring Boot 后端本文以项目名称joy-admin-backend为例搭建一套可重复使用的 Jenkins 自动部署流程包含Docker 部署 JenkinsJenkins 通过 SSH 拉取阿里云 Codeup 代码JDK 21、Maven 3.9.9 构建Docker Compose 发布 Spring Boot 后端版本化 Docker 镜像健康检查发布失败自动回滚手动回滚历史版本后端仓库gitcodeup.aliyun.com:xxxxx/xxxxx.git一、整体架构Codeup ↓ SSH Docker Jenkins ↓ docker compose build joy-admin-backend:构建号-GitCommit ↓ docker compose up 后端容器 ↓ 健康检查失败 自动恢复上一个 Docker 镜像代码每次从 Codeup 拉取环境配置保存在 Jenkins Credentials不依赖本机已有项目。二、环境要求推荐配置Docker Desktop / Docker Engine Docker Compose 2.20 Jenkins LTS JDK 21 Maven 3.9.9 至少 4 核 CPU 至少 6 GB 内存 至少 50 GB 可用磁盘检查环境1. 检查机器环境 执行uname-msw_verssysctl-nhw.memsizedf-h/uname-m输出 arm64Apple Silicon 或 x86_64Intel2. 检查Docker 先启动Docker Desktop然后执行dockerversiondockercompose versiondockerinfo--formatOS{{.OSType}}, Arch{{.Architecture}}, CPUs{{.NCPU}}, Memory{{.MemTotal}}dockerrun--rmhello-world 要求dockerversion同时显示Client和Server。dockercompose version正常输出。 hello-world运行成功。 Docker运行Linux容器。3. 检查构建镜像dockerrun--rmeclipse-temurin:21-jdkjava-versiondockerrun--rm\maven:3.9.9-eclipse-temurin-21\mvn-versiondockerrun--rm\node:16.20.2-bullseye\sh-cnode -v npm -v预期输出 Java21Apache Maven3.9.9 Node v16.20.2npm8.x4. 检查Maven仓库挂载 先确认目录存在ls-ld/Users/mac/Documents/work/repo 测试容器读取dockerrun--rm\-v/Users/mac/Documents/work/repo:/maven-repo:ro\alpine:3.20\sh-ctest -d /maven-repo ls -la /maven-repo | head如果出现Mounts denied在Docker Desktop中打开 Settings → Resources → File Sharing 加入 /Users/mac/Documents/work 然后Apply并重启Docker Desktop。5. 检查端口lsof-nP-iTCP:8080-sTCP:LISTENlsof-nP-iTCP:1024-sTCP:LISTENlsof-nP-iTCP:9080-sTCP:LISTEN规划8080Jenkins1024Joy Admin前端9080后端仅在需要本机调试时映射 没有输出代表端口空闲。如果8080已占用后面改用18080。三、配置 Codeup SSH1. 生成 Jenkins 专用密钥ssh-keygen\-ted25519\-Cjenkins-codeup\-f~/.ssh/joy-admin-jenkins-codeup密钥位置~/.ssh/joy-admin-jenkins-codeup 私钥 ~/.ssh/joy-admin-jenkins-codeup.pub 公钥登陆Aliyun Codeup 的个人设置 》添加 SSH 公钥将.pub公钥内容添加进去。2. 验证 Codeup 主机指纹(机子能否链接到Codeup拉取代码)Codeup 返回的是 RSA 主机密钥ssh-keyscan-T10-trsa codeup.aliyun.com\/tmp/codeup_known_hosts ssh-keygen-lf/tmp/codeup_known_hosts返回下面官方 SHA256 指纹就说明是正常的SHA256:yEGmgQNVrc3QAvDvoBrTCF2s07KwmmQAbWi9vSt/fE验证访问codeupssh-T\-i~/.ssh/joy-admin-jenkins-codeup\-oIdentitiesOnlyyes\gitcodeup.aliyun.com出现Welcome to Codeup表示成功。验证访问自己的仓库GIT_SSH_COMMANDssh -i ~/.ssh/joy-admin-jenkins-codeup -o IdentitiesOnlyyes\gitls-remote\gitcodeup.aliyun.com:xxxxxx/xxxxxx.git\refs/heads/main出现类似 9e7f396c0226cfa7cfebefeeebcef341752cb58 refs/heads/main 表示成功。以上确认完成后加入本机信任列表mkdir -p ~/.ssh chmod 700 ~/.ssh touch ~/.ssh/known_hosts chmod 600 ~/.ssh/known_hosts ssh-keygen -F codeup.aliyun.com /dev/null \ || cat /tmp/codeup_known_hosts ~/.ssh/known_hosts四、创建 Jenkins Docker 环境目录结构预览joy-admin-deploy/ ├── .env ├── compose.jenkins.yml └── jenkins/ ├── Dockerfile └── known_hosts创建上面的预览目录结构mkdir-p/Users/mac/Documents/work/joy-admin-deploy/jenkinscd/Users/mac/Documents/work/joy-admin-deploycp/tmp/codeup_known_hosts jenkins/known_hostschmod644jenkins/known_hosts1. 创建Jenkins镜像Jenkins Dockerfile可以使用命令创建nanojenkins/Dockerfile写入FROM maven:3.9.9-eclipse-temurin-21 AS maven_tool FROM node:16.20.2-bullseye AS node_tool FROM jenkins/jenkins:lts-jdk21 USER root RUN printf %s\n \ Acquire::Retries 5; \ Acquire::http::Timeout 60; \ Acquire::https::Timeout 60; \ /etc/apt/apt.conf.d/80-network-retries \ sed -i \ s|http://deb.debian.org|https://deb.debian.org|g \ /etc/apt/sources.list.d/debian.sources \ apt-get update \ apt-get install -y --no-install-recommends \ ca-certificates curl gnupg git openssh-client rsync \ install -m 0755 -d /etc/apt/keyrings \ curl --retry 5 --retry-delay 3 --retry-all-errors \ -fsSL https://download.docker.com/linux/debian/gpg \ -o /etc/apt/keyrings/docker.asc \ chmod ar /etc/apt/keyrings/docker.asc \ echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.asc] \ https://download.docker.com/linux/debian \ $(. /etc/os-release echo $VERSION_CODENAME) stable \ /etc/apt/sources.list.d/docker.list \ apt-get update \ apt-get install -y --no-install-recommends \ docker-ce-cli \ docker-buildx-plugin \ docker-compose-plugin \ rm -rf /var/lib/apt/lists/* COPY --frommaven_tool /usr/share/maven /usr/share/maven COPY --fromnode_tool /usr/local /usr/local ENV MAVEN_HOME/usr/share/maven ENV PATH/usr/share/maven/bin:/usr/local/bin:${PATH} RUN mkdir -p /root/.ssh \ chmod 700 /root/.ssh \ java -version \ mvn -version \ node -v \ npm -v \ docker --version \ docker buildx version \ docker compose version RUN jenkins-plugin-cli --plugins \ workflow-aggregator \ pipeline-stage-view \ git \ ssh-credentials \ ssh-agent \ credentials-binding \ docker-workflow \ timestamper \ ws-cleanup USER rootdocker-buildx-plugin不能省略因为目前我的后端项目Dockerfile配置了RUN --mounttypecache,target/root/.m2该语法只能通过 BuildKit 执行。2. Jenkins Compose创建compose.jenkins.ymlservices:jenkins:build:context:./jenkinsdockerfile:Dockerfileimage:joy-jenkins:lts-jdk21container_name:joy-jenkinsuser:rootrestart:unless-stoppedports:-${JENKINS_BIND_ADDRESS}:${JENKINS_HTTP_PORT}:8080environment:TZ:UTCJAVA_OPTS:--Duser.timezoneUTC-Djenkins.install.runSetupWizardtruevolumes:-jenkins_home:/var/jenkins_home-jenkins_npm_cache:/root/.npm-${HOST_MAVEN_REPO}:/maven-repo-/var/run/docker.sock:/var/run/docker.sock-./jenkins/known_hosts:/root/.ssh/known_hosts:rovolumes:jenkins_home:name:joy_jenkins_homejenkins_npm_cache:name:joy_jenkins_npm_cache创建.envJENKINS_BIND_ADDRESS127.0.0.1 JENKINS_HTTP_PORT8080 HOST_MAVEN_REPO/Users/mac/Documents/work/repo在其他机器构建时只需要修改HOST_MAVEN_REPO。3. 构建并启动 Jenkinscd/Users/mac/Documents/work/joy-admin-deploy 构建(如果compose.jenkins.yml或者Dockfile有改动这个命令也可以重新构建)dockercompose\--env-file .env\-fcompose.jenkins.yml\build--pull启动dockercompose\--env-file .env\-fcompose.jenkins.yml\up-d检查状态dockercompose\--env-file .env\-fcompose.jenkins.yml\psdockerlogs--tail100joy-jenkins获取初始化密码dockerexecjoy-jenkins\cat/var/jenkins_home/secrets/initialAdminPassword打开http://127.0.0.1:8080完成管理员创建和推荐插件安装。验证工具dockerexecjoy-jenkinsbash-lc java -version mvn -version node -v npm -v git --version docker version docker buildx version docker compose version 验证Jenkins中的主机指纹:dockerexecjoy-jenkins\ssh-keygen-lf/root/.ssh/known_hosts输出必须包含类似SHA256:yEGmgQNVrc3QAvDvoBrTCF2s07KwmmQAbWi9vSt/fE五、配置 Jenkins 凭据http://127.0.0.1:8080先登陆Jenkins1. Codeup 私钥凭据进入Manage Jenkins → Credentials → System → Global credentials → Add Credentials填写KindSSH Username with private key Usernamegit #可以命令行 pbcopy ~/.ssh/joy-admin-jenkins-codeup 复制内容 Private Key粘贴不带 .pub 后缀的私钥 IDcodeup-ssh DescriptionCodeup只读拉取凭据注意joy-admin-jenkins-codeup 私钥可以填入Jenkins joy-admin-jenkins-codeup.pub 公钥不能填入Jenkins如果错误粘贴公钥会出现Load key: error in libcrypto Permission denied (publickey)2. 后端环境配置凭据根据实际生成.env关键配置示例以下内容是我的后端项目里面已有的内容我就直接使用了你们根据自己的配置来生成.env内容JOY_ADMIN_IMAGEjoy-admin-backend JOY_ADMIN_IMAGE_TAGlatest JOY_ADMIN_BIND_ADDRESS127.0.0.1 JOY_ADMIN_HOST_PORT9080 JOY_ADMIN_DATA_DIR/Users/mac/Documents/work/joy-admin-runtime/backend-data JAVA_TOOL_OPTIONS-XX:MaxRAMPercentage75.0 -XX:InitialRAMPercentage25.0 -XX:ExitOnOutOfMemoryError -Djava.io.tmpdir/app/tmp -Duser.timezoneUTC BUSINESS_TIME_ZONEAsia/Kolkata DB_URLjdbc:postgresql://数据库地址:5432/onevone?options-c%20TimeZoneUTCsslmodepreferconnectTimeout10socketTimeout60tcpKeepAlivetrue DB_USER数据库用户 DB_PASSWORD数据库密码 REDIS_HOSTRedis地址 REDIS_PORT6379 REDIS_DATABASE0 REDIS_USERNAME REDIS_PASSWORD REDIS_SSLfalse QUARTZ_INSTANCE_NAMEQuartzScheduler-admin-test QUARTZ_CLUSTEREDtrue TOKEN_SECRET随机安全密钥 UPSTREAM_SERVICE_URL实际服务地址 UPSTREAM_SERVICE_SIGNATURE实际签名 OSS_PREFIX_CDN OSS_PREFIX_OSS OSS_PREFIX_HTTP OSS_BUCKET_NAME OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET OSS_ENDPOINT SWAGGER_ENABLEDfalse DRUID_CONSOLE_ENABLEDfalse如果数据库或 Redis 运行在 Docker 宿主机容器内地址使用host.docker.internal创建持久化目录mkdir-p/Users/mac/Documents/work/joy-admin-runtime/backend-data将.env上传到 JenkinsKindSecret file File选择准备好的.env IDjoy-admin-backend-env DescriptionJoy Admin后端运行配置.env本地文件路径只在上传时使用Jenkins 保存的是文件内容流水线不会依赖上传路径。六、创建后端 Pipeline创建任务New Item → joy-admin-backend → PipelinePipeline Scriptpipeline{agent any parameters{choice(name:ACTION,choices:[DEPLOY,ROLLBACK],description:发布新版本或回滚旧版本)string(name:GIT_BRANCH,defaultValue:main,description:构建分支)string(name:ROLLBACK_TAG,defaultValue:,description:回滚标签例如15-a8c37f21)}options{timestamps()timeout(time:60,unit:MINUTES)disableConcurrentBuilds()skipDefaultCheckout(true)buildDiscarder(logRotator(numToKeepStr:30))}environment{IMAGE_NAMEjoy-admin-backendDOCKER_BUILDKIT1TZUTC}stages{stage(拉取代码){steps{deleteDir()sshagent(credentials:[codeup-ssh]){sh set -eu export GIT_SSH_COMMANDssh \ -o StrictHostKeyCheckingyes \ -o UserKnownHostsFile/root/.ssh/known_hosts git clone \ --depth 1 \ --branch $GIT_BRANCH \ gitcodeup.aliyun.com:xxxxx/xxxxx.git \ backend }script{env.GIT_SHORTsh(script:git -C backend rev-parse --short8 HEAD,returnStdout:true).trim()env.NEW_TAG${env.BUILD_NUMBER}-${env.GIT_SHORT}}}}stage(准备环境配置){steps{withCredentials([file(credentialsId:joy-admin-backend-env,variable:BACKEND_ENV_FILE)]){sh set -eu install -m 600 $BACKEND_ENV_FILE backend/.env }}}stage(执行发布或回滚){steps{dir(backend){script{if(params.ACTIONROLLBACK){if(!params.ROLLBACK_TAG.trim()){error(回滚必须填写ROLLBACK_TAG)}env.TARGET_TAGparams.ROLLBACK_TAG.trim()sh set -eu export JOY_ADMIN_IMAGE$IMAGE_NAME export JOY_ADMIN_IMAGE_TAG$TARGET_TAG docker image inspect \ $IMAGE_NAME:$TARGET_TAG /dev/null docker compose up \ -d --no-build --force-recreate \ --wait --wait-timeout 300 \ joy-admin CONTAINER_ID$(docker compose ps -q joy-admin) docker exec $CONTAINER_ID \ curl --fail --silent \ http://127.0.0.1:9080/ /dev/null echo 回滚成功$IMAGE_NAME:$TARGET_TAG }else{env.TARGET_TAGenv.NEW_TAG sh set -u export JOY_ADMIN_IMAGE$IMAGE_NAME export JOY_ADMIN_IMAGE_TAG$TARGET_TAG CURRENT_ID$( docker compose ps -q joy-admin 2/dev/null || true ) PREVIOUS_IMAGE if [ -n $CURRENT_ID ]; then PREVIOUS_IMAGE$( docker inspect \ --format {{.Config.Image}} \ $CURRENT_ID ) fi docker compose config /dev/null || exit 1 docker compose build --pull joy-admin || exit 1 docker image inspect \ $IMAGE_NAME:$TARGET_TAG /dev/null || exit 1 DEPLOY_OKfalse if docker compose up \ -d --no-build \ --wait --wait-timeout 300 \ joy-admin then CONTAINER_ID$( docker compose ps -q joy-admin ) if docker exec $CONTAINER_ID \ curl --fail --silent \ http://127.0.0.1:9080/ /dev/null then DEPLOY_OKtrue fi fi if [ $DEPLOY_OK true ]; then echo 发布成功$IMAGE_NAME:$TARGET_TAG docker inspect \ --format镜像{{.Config.Image}} 健康{{.State.Health.Status}} \ $CONTAINER_ID exit 0 fi echo 新版本启动失败 docker compose logs --tail200 joy-admin || true if [ -z $PREVIOUS_IMAGE ]; then echo 首次发布没有旧版本可回滚 exit 1 fi if ! docker image inspect \ $PREVIOUS_IMAGE /dev/null 21 then echo 旧镜像不存在$PREVIOUS_IMAGE exit 1 fi PREVIOUS_TAG${PREVIOUS_IMAGE##*:} export JOY_ADMIN_IMAGE_TAG$PREVIOUS_TAG if docker compose up \ -d --no-build --force-recreate \ --wait --wait-timeout 300 \ joy-admin then echo 已自动回滚$PREVIOUS_IMAGE else echo 自动回滚失败需要人工处理 fi exit 1 }}}}}}post{always{shrm -f backend/.env 2/dev/null || truedeleteDir()}}}七、执行第一次发布选择Build with Parameters填写ACTIONDEPLOY GIT_BRANCHmain ROLLBACK_TAG留空每次发布生成唯一镜像joy-admin-backend:构建号-Git短Commit例如joy-admin-backend:7-9c0226cf joy-admin-backend:8-a321bc42八、验证发布dockerps--filternamejoy-admin-backenddockerimages joy-admin-backendcurlhttp://127.0.0.1:9080/查看当前运行镜像dockerinspect\--format{{.Config.Image}}\$(dockerps-q--filternamejoy-admin-backend)查看日志dockerlogs\--tail200\$(dockerps-q--filternamejoy-admin-backend)九、执行回滚查看历史镜像dockerimages joy-admin-backend 比如返回所有镜像 joy-admin-backend15-a8c37f21 joy-admin-backend16-d529bf10登陆Jenkins再次选择Build with Parameters参数界面填入ACTIONROLLBACK GIT_BRANCHmain ROLLBACK_TAG需要恢复的镜像标签如 15-a8c37f21回滚使用已有镜像不重新编译。发布新版本健康检查失败时流水线会自动恢复发布前正在运行的镜像。十、常见问题1. ssh-keyscan提示不是公钥文件错误(stdin) is not a public key file原因是 Codeup 没有提供 ED25519 主机密钥只提供 RSAssh-keyscan-T10-trsa codeup.aliyun.com2. Jenkins拉取代码报libcrypto错误错误Load key: error in libcrypto Permission denied (publickey)检查是否错误上传了.pub公钥。Jenkins需要不带.pub后缀的私钥。3. Debian下载返回502错误502 Bad Gateway Unable to fetch some archivesJenkins Dockerfile中配置APT重试和HTTPS源重新执行构建即可。4. BuildKit不可用错误Docker Compose requires buildx plugin the --mount option requires BuildKitJenkins镜像必须安装docker-buildx-plugin验证dockerexecjoy-jenkinsdockerbuildx version5. 容器无法连接宿主机数据库或Redis容器中的127.0.0.1表示容器自己。Mac和Docker Desktop使用host.docker.internalLinux通过现有Compose配置extra_hosts:-host.docker.internal:host-gateway6. 测试失败导致构建失败目前我的后端Dockerfile使用-Dmaven.test.skiptrue生产镜像构建不会运行测试。测试应作为独立质量阶段修复后再决定是否阻断发布。十一、迁移到另一台机器新机器需要安装Docker和Docker Compose。创建同样的joy-admin-deploy目录。构建并启动Jenkins。配置同样的known_hosts。配置凭据codeup-ssh。上传新机器对应环境的.env凭据ID仍为joy-admin-backend-env。创建同样的Pipeline任务。创建JOY_ADMIN_DATA_DIR目录。执行ACTIONDEPLOY。流水线不依赖本地项目目录所有代码均从Codeup获取。十二、安全和维护建议.env禁止提交Git。Jenkins不要直接暴露公网。Jenkins挂载Docker Socket后拥有宿主机Docker管理权限。定期备份joy_jenkins_home卷。至少保留最近5至10个后端镜像。不要随意执行docker system prune -a用于彻底清理 Docker 系统中未使用的资源包括未被任何容器引用的镜像、停止的容器。Docker镜像回滚不会回滚数据库。SQL变更应保持新旧应用版本兼容。跨机器保留历史镜像时需要增加Docker Registry。查看Docker磁盘占用dockersystemdf官方参考Jenkins Docker安装Jenkins PipelineDocker ComposeCodeup SSH密钥配置