Windows 使用 Docker Compose + Jenkins 自动部署 Spring Boot 后端

发布时间:2026/8/11 18:36:06
Windows 使用 Docker Compose + Jenkins 自动部署 Spring Boot 后端 Windows 使用 Docker Compose Jenkins 自动部署 Spring Boot 后端本文以 joy-admin-backend 为例将 Mac/Linux 流程迁移到 Windows 10/11。发布链路为Codeup - JenkinsDocker 容器 - Docker BuildKit - Docker Compose - 后端容器Jenkins 运行在 Docker Desktop 的 Linux 容器内因此 Pipeline 中继续使用 Linux 的 sh只有 Windows 主机命令使用 PowerShell。1. 环境准备安装 Windows 10 22H2 或 Windows 11、Docker Desktop 和 WSL2。Docker Desktop 的 Settings - General 中开启 Use the WSL 2 based engine并使用 Linux containers。管理员 PowerShellwsl--install wsl--update wsl--set-default-version 2 wsl--version重启后检查wsl--status docker version docker compose version docker info--formatOS{{.OSType}}, Arch{{.Architecture}}, CPUs{{.NCPU}}, Memory{{.MemTotal}}docker run--rmhello-world验证构建工具docker run--rmeclipse-temurin:21-jdk java-version docker run--rmmaven:3.9.9-eclipse-temurin-21 mvn-version docker run--rmnode:16.20.2-bullseye sh-cnode -v npm -v先手动创建统一工作目录D:/work然后使用 PowerShell 创建所需子目录New-Item-ItemType Directory-Force-PathD:/work/joy-admin-deploy/jenkins,D:/work/ssh,D:/work/joy-admin-backend-deploy/dataWSL2 模式通常会自动处理 Windows 盘符挂载如果出现 Mounts denied根据 Docker Desktop 版本在 Settings - Resources - File sharing 或 Settings - Resources - WSL Integration 中加入D:/work后重启。检查端口Get-NetTCPConnection-LocalPort 8080,1024,9080-State Listen-ErrorAction SilentlyContinue规划8080 Jenkins1024 前端9080 后端。2. 配置 Codeup SSHWindows 通常自带 OpenSSH Client。使用 PowerShell$SshDirD:/work/sshNew-Item-ItemType Directory-Force-Path$SshDirssh-keygen-t ed25519-Cjenkins-codeup-f$SshDir/joy-admin-jenkins-codeup将 joy-admin-jenkins-codeup.pub 内容添加到 Codeup 的个人设置 - SSH 公钥。私钥只上传到 Jenkins。获取并核对主机指纹$KnownHostsD:/work/joy-admin-deploy/jenkins/known_hostsssh-keyscan-T 10-t rsa codeup.aliyun.com|Set-Content-Encoding ascii$KnownHostsssh-keygen-lf$KnownHosts必须核对阿里云 Codeup 官方 RSA 指纹SHA256:yEGmgQNVrc3QAvDvoBrTCxxxxxxxxxxAbWi9vSt/fE。不一致时不要继续使用该信任文件。验证$KeyD:/work/ssh/joy-admin-jenkins-codeupssh-T-i$Key-o IdentitiesOnlyyes gitcodeup.aliyun.com$env:GIT_SSH_COMMAND ssh -i$Key-o IdentitiesOnlyyesgitls-remote gitcodeup.aliyun.com:组织/仓库.git refs/heads/mainRemove-ItemEnv:GIT_SSH_COMMAND3. 创建 Jenkins 容器目录D:/work/joy-admin-deploy/ ├── .env ├── compose.jenkins.yml └── jenkins/ ├── Dockerfile └── known_hostsjenkins/DockerfileFROM 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 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。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-/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构建并启动Set-LocationD:/work/joy-admin-deploydocker compose--env-file.env-f compose.jenkins.yml build--pull docker compose--env-file.env-f compose.jenkins.yml up-d docker compose--env-file.env-f compose.jenkins.ymlpsdocker logs--tail 100 joy-jenkins docker exec joy-jenkinscat/var/jenkins_home/secrets/initialAdminPassword访问 http://127.0.0.1:8080 完成 Jenkins 初始化。验证工具docker exec joy-jenkins bash-lcjava -version; mvn -version; node -v; npm -v; git --version; docker version; docker buildx version; docker compose versiondocker exec joy-jenkins ssh-keygen-lf/root/.ssh/known_hosts4. Jenkins 凭据进入 Manage Jenkins - Credentials - System - Global credentials。Codeup 凭据KindSSH Username with private key Usernamegit Private Keyjoy-admin-jenkins-codeup不带 .pub IDcodeup-ssh后端环境文件作为 Secret file 上传ID 为 joy-admin-backend-env。示例# Docker image and host binding JOY_ADMIN_IMAGEjoy-admin-backend JOY_ADMIN_IMAGE_TAGlatest JOY_ADMIN_BIND_ADDRESS0.0.0.0 JOY_ADMIN_HOST_PORT9080 # Docker宿主机公共工作根目录 HOST_MACHINE_ROOTD:/work/joy-admin-backend-deploy # Docker公共工作根目录 DOCKER_ROOTD:/work/joy-admin-backend-deploy # JVM. Adjust these percentages to the server capacity. JAVA_TOOL_OPTIONS-XX:MaxRAMPercentage75.0 -XX:InitialRAMPercentage25.0 -XX:ExitOnOutOfMemoryError -Djava.io.tmpdir${DOCKER_ROOT}/tmp # Business calendar. JVM/database timestamps remain UTC; business dates use this zone. BUSINESS_TIME_ZONEAsia/Kolkata # Remote PostgreSQL. Use a private IP or internal DNS name. # For TLS, use sslmoderequire or preferably verify-full with the required certificates. DB_URLjdbc:postgresql://192.168.1.114:5432/onevone?sslmodedisableconnectTimeout10socketTimeout120tcpKeepAlivetrue DB_USERonevone DB_PASSWORDonevone DB_POOL_INITIAL_SIZE5 DB_POOL_MIN_IDLE10 DB_POOL_MAX_ACTIVE20 # Remote Redis. Never expose Redis directly to the public internet. REDIS_HOSThost.docker.internal REDIS_PORT6379 REDIS_DATABASE3 REDIS_USERNAME REDIS_PASSWORD REDIS_SSLfalse REDIS_TIMEOUT10s # Quartz uses the PostgreSQL connection above. QUARTZ_INSTANCE_NAMEQuartzScheduler-admin-production QUARTZ_CLUSTEREDtrue QUARTZ_THREAD_COUNT10 QUARTZ_DB_MAX_CONNECTIONS10 # Application security. Generate a long random value for production. TOKEN_SECRETreplace_with_a_long_random_secret TOKEN_EXPIRE_MINUTES43200 # Upstream service called by joy-admin. UPSTREAM_SERVICE_URLhttp://localhost:9080 UPSTREAM_SERVICE_SIGNATUREreplace_me # Optional external integrations. Leave blank only when the feature is unused. OSS_PREFIX_CDN OSS_PREFIX_OSS OSS_PREFIX_HTTP OSS_BUCKET_NAME OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET OSS_ENDPOINT FEISHU_WEBHOOK_URL # Production switches SWAGGER_ENABLEDfalse DRUID_CONSOLE_ENABLEDfalse DRUID_CONSOLE_USERNAME DRUID_CONSOLE_PASSWORD # 开放域名CORS跨域 CORS_ALLOWED_ORIGINShttps://xxx.com # Docker宿主机持久化目录 # JOY_ADMIN_LOG_DIRDocker宿主机路径 JOY_ADMIN_DATA_DIR${HOST_MACHINE_ROOT}/data JOY_ADMIN_LOG_DIR${HOST_MACHINE_ROOT}/logs # Docker持久化目录 # DOCKER_LOG_DIRDocker路径 DOCKER_DATA_DIR${DOCKER_ROOT}/data DOCKER_LOG_DIR${DOCKER_ROOT}/logs # Docker容器内部日志路径 # APP_LOG_FILE容器内部Linux路径 APP_LOG_FILE${DOCKER_LOG_DIR}/joy-admin.log APP_LOG_FILE_PATTERN${DOCKER_LOG_DIR}/archive/joy-admin.%d{yyyy-MM-dd}.%i.log.gz APP_LOG_LEVELinfo APP_LOG_MAX_FILE_SIZE100MB APP_LOG_MAX_HISTORY30 APP_LOG_TOTAL_SIZE_CAP3GB如果数据库或 Redis 在 Windows 主机容器内使用 host.docker.internal不能使用 127.0.0.1。流水线结束后应删除工作区中的 backend/.env。5. Pipeline 发布创建 New Item - joy-admin-backend - Pipeline。沿用原 Mac/Linux Pipeline Script只替换 Codeup 仓库地址git clone--depth1--branch$GIT_BRANCH\ gitcodeup.aliyun.com:组织/仓库.git backend必须保留 codeup-ssh、joy-admin-backend-env、DOCKER_BUILDKIT1、BUILD_NUMBER-GIT_SHORT 镜像标签、docker compose build --pull、docker compose up --wait --wait-timeout 300、健康检查失败自动回滚、ACTIONROLLBACK 使用已有镜像以及 post 阶段删除 .env 和工作区。不要将 Pipeline 中的 sh 改成 bat。Pipeline 在 Linux Jenkins 容器中执行。完整 Pipeline Script以下脚本可以直接粘贴到 Jenkins替换仓库地址即可pipeline{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:组织/仓库.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()}}}首次发布参数ACTIONDEPLOY GIT_BRANCHmain ROLLBACK_TAG留空6. 验证和回滚dockerps--filternamejoy-admindocker images joy-admin-backend docker logs--tail 200(dockerps-q--filternamejoy-admin)docker inspect--format{{.Config.Image}}(dockerps-q--filternamejoy-admin)docker inspect--format{{range .Mounts}}{{println .Source - .Destination}}{{end}}(dockerps-q--filternamejoy-admin)回滚参数ACTIONROLLBACK GIT_BRANCHmain ROLLBACK_TAG15-a8c37f21回滚只切换已有镜像不回滚数据库。7. 常见问题挂载目录失败Test-PathD:/work/joy-admin-backend-deploy/data确认 Docker Desktop 文件共享设置、目录存在且没有使用网络映射盘或复杂特殊字符路径。发布后还要通过 docker inspect 确认 /app/data 的 Source 指向预期 Windows 目录。这是 Jenkins 容器通过 Docker Socket 调用 Docker Desktop daemon 的场景挂载源必须能被 Docker Desktop daemon 识别。若日志提示 invalid mount config 或 /Users/mac/Documents/work/joy-admin-backend-deploy/data 没有指向 D:/work可在后端仓库增加 compose.windows.ymlservices:joy-admin:volumes:-joy_admin_backend_data:/Users/mac/Documents/work/joy-admin-backend-deploy/datavolumes:joy_admin_backend_data:name:joy_admin_backend_data然后将 Pipeline 的每个命令改为docker compose -f compose.yml -f compose.windows.yml subcommand并从后端环境文件中删除 JOY_ADMIN_DATA_DIR。named volume 由 Docker Desktop 管理不依赖 Jenkins 容器解析 Windows 盘符。Jenkins 找不到 Dockerdocker exec joy-jenkins docker version docker exec joy-jenkins docker buildx version确认 Docker Desktop 正在运行并保留 /var/run/docker.sock 挂载。Codeup 认证失败确认 Jenkins 上传的是私钥而不是 .pubknown_hosts 包含 codeup.aliyun.comPipeline 凭据 ID 为 codeup-ssh。数据库或 Redis 连接失败容器中的 127.0.0.1 指向容器自身。Windows 主机服务使用 host.docker.internal远程服务使用局域网 IP 或 DNS。不要将数据库和 Redis 暴露到公网。/prod-api 返回 403 或 404Cloudflare Tunnel 不会自动删除 /prod-api。Tunnel 直接指向后端 9080 时应让 Nginx 转发location /prod-api/ { proxy_pass http://127.0.0.1:9080/; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }跨域部署时后端环境文件必须配置准确的 CORS_ALLOWED_ORIGINS。8. 迁移和维护迁移到另一台 Windows安装 Docker Desktop 和 WSL2创建相同目录复制 Compose、Dockerfile、known_hosts重新配置两个 Jenkins 凭据然后执行 DEPLOY。流水线每次从 Codeup 拉取代码不依赖本地项目目录。安全注意事项.env 和 SSH 私钥禁止提交 GitJenkins 只绑定 127.0.0.1不直接暴露公网Jenkins 挂载 Docker Socket 后拥有宿主机 Docker 管理权限定期备份 joy_jenkins_home 和业务数据保留最近 5 至 10 个镜像不要随意执行 docker system prune -a镜像回滚不会回滚数据库跨机器保留镜像时使用私有 Docker Registry。docker system df参考Jenkins Docker、Jenkins Pipeline、Docker Compose、Codeup SSH。