ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

若依后端Docker化部署实战:Spring Boot+MySQL+Redis容器编排

若依后端Docker化部署实战:Spring Boot+MySQL+Redis容器编排 手头一个内部管理系统是基于若依前后端分离版开发的交付准备阶段客户只给了台全新测试机要求把后端、数据库、缓存一次性跑起来。当时临时去查各种教程断断续续折腾了两天后来把整个流程整理成一份可直接复用的部署方案。这篇指南就把这套方案完整写出来涉及镜像选型、Dockerfile 编写、Compose 编排、常见坑位排查适合需要把若依后端以 Docker 方式部署到服务器或本地开发机的读者。后端环境是 Spring Boot MySQL Redis 的经典组合你照着一步步操作最后能得到一个可随时重建的后端运行环境。1. 部署前的准备与整体思路1.1 若依后端容器化要解决什么问题若依RuoYi本身是一套基于 Spring Boot 的后台权限管理系统代码里包含了用户、角色、菜单、字典、定时任务等一堆通用模块。平时本地开发电脑上得装 JDK、Maven、MySQL 5.7 或 8.0、Redis还要把数据库脚本手工导入。问题在于每个人电脑环境不一样JDK 版本不同、MySQL 字符集不同、Redis 有没有密码也说不清协作时经常出现“我这跑得好好的你那怎么报错”。Docker 化部署的核心价值不是“看起来很现代”而是把整个运行环境固化成文件。前端分离开发完成后后端 jar 包、MySQL 初始化脚本、Redis 密码、端口映射、环境变量全部通过 Dockerfile 和 Compose 文件描述出来。任何人拿到项目目录执行一条docker compose up -d就能得到与开发环境基本一致的后端运行态。交付、回滚、扩容都会轻松很多。这里我也要提醒一句容器化解决的是“环境不一致”问题但不解决“代码本来就有 bug”的问题。先把业务功能在本地跑通再来做 Docker 化别指望容器来兜底。1.2 整体架构与容器清单若依前后端分离版的后端是一个标准 Spring Boot 单体应用依赖 MySQL 存业务数据依赖 Redis 放验证码、登录 token、在线用户、定时任务锁这一类缓存数据。本次部署方案里我会创建三个容器容器镜像职责ruoyi-mysqlmysql:8.0存储全部业务表、菜单权限、定时任务配置ruoyi-redisredis:7.0-alpine缓存登录用户信息、验证码、sessionruoyi-backend基于 JDK8 自定义镜像运行 ruoyi-admin.jar提供 REST API我没有选择把 MySQL 和 Redis 部署在宿主机上而是统一容器化原因有三个一是交付环境是台干净测试机宿主机不一定预装数据库二是 MySQL 数据目录和 Redis 数据目录都能用 volume 持久化后续换机器直接挂上旧数据就能恢复三是整个依赖关系可以用 Compose 的depends_on和健康检查控制启动顺序避免后端容器起来时数据库还没初始化好。1.3 环境准备与项目初始状态动手之前先确认几件事。第一Docker 版本建议不低于 20.10Compose 插件最好用 v2 版本也就是docker compose中间没横杠这种命令。第二Windows 用户建议直接用 Docker Desktop 并启用 WSL2 后端Linux 用户装 docker-ce 即可。第三准备好若依前后端分离版的源码至少包含后端代码和sql目录下的数据库脚本通常是一个ry_2024xxxx.sql和一个quartz.sql。还需要在后端源码里确认一点若依是典型的 Maven 多模块工程模块名包括ruoyi-admin、ruoyi-common、ruoyi-framework、ruoyi-generator、ruoyi-quartz、ruoyi-system真正可执行的 Spring Boot 入口在ruoyi-admin模块。所以后面 Dockerfile 里拷贝 jar 时路径要写成ruoyi-admin/target/ruoyi-admin.jar千万别拿整个工程根目录当成启动目录。2. 基础镜像选型和依赖组件配置2.1 JDK 镜像的选择逻辑若依 3.8.x 依赖的 Spring Boot 版本主要是 2.x在 JDK 8 下运行是最省心的。我自己做镜像时优先选择eclipse-temurin:8-jre而不是openjdk:8-jre-alpine原因是 Alpine 发行版用的是 musl libc个别 Java 类库在做文件操作、时区计算时会出现难以描述的异常排查成本极高。如果你在本地测试时没发现问题生产环境最好也别赌这个概率选 Debian 或 Ubuntu 底座的 JRE 镜像更可靠。这里顺便说一下镜像体积问题。完整 JDK 镜像动不动就五六百兆运行时其实只需要 JRE。eclipse-temurin:8-jre的体积比 JDK 小很多装完 jar 后镜像大概二三百兆对你没坏处。构建时用 Maven 容器完成编译运行镜像里不保留 JDK、不保留源码这也是后面多阶段构建要做的事。2.2 MySQL 8.0 容器的初始化安排MySQL 的 Docker 镜像有一个官方支持的初始化行为容器首次启动时会自动执行/docker-entrypoint-initdb.d目录下的.sql、.sh文件。若依源码的sql目录里有两个脚本命名大致是ry_2024xxxx.sql和quartz.sql把整个sql目录挂载到/docker-entrypoint-initdb.d首次启动时两个脚本会按文件名字母序执行。宁可让文件名保持像1_ry.sql、2_quartz.sql这样的可排序前缀也不要依赖目录顺序。创建数据库时要注意字符集。若依默认要求 utf8mb4否则中文、表情符号、特殊字符都可能出现乱码或存储异常。Compose 里给 MySQL 容器加command参数指定--character-set-serverutf8mb4和--collation-serverutf8mb4_general_ci这部分我会在后面的编排文件里直接给出完整写法。还有一点MySQL 8.0 默认的认证插件是caching_sha2_password如果你的后端 JDBC 驱动版本比较老会出现 “Public Key Retrieval is not allowed” 这类报错。这个问题我在第 6 节会专门展开先记住一个思路在 JDBC 连接串上增加allowPublicKeyRetrievaltrueuseSSLfalse同时依赖若依自带的 JDBC 驱动版本一般都能解决。2.3 Redis 单节点配置要点若依后端对 Redis 的要求不高主要存验证码、登录令牌和在线用户信息单节点完全够用不需要主从复制。但是 Redis 容器必须开启密码认证否则后端连接周期会可能会被暴露尤其是测试机映射了公网端口时特别危险。在 Compose 文件里给 Redis 容器加启动命令即可redis-server --requirepass redis123456 --appendonly yes--appendonly yes表示开启 AOF 持久化配合 volume 挂载容器销毁后数据还能保留。后端application.yml里的 Redis 密码要与此一致我会把密码也做成环境变量这样不同环境切换时不用改 jar 包里的配置。3. 编写 Dockerfile 构建后端镜像3.1 多阶段构建的核心思路后端镜像如果只写一层“拷贝 jar 运行”那下载依赖和编译还得在宿主机完成换一台机器就又得装 Maven。多阶段构建的思路是第一个阶段用 Maven 镜像把源码编译成 jar第二个阶段用精简 JRE 镜像只拷 jar最终镜像里不含 Maven、不含源码、不含编译缓存。这样有几个直接好处镜像体积小、交付内容干净、构建过程可重复。坏处是每次构建都要重新拉依赖第一次会慢一些。如果经常改代码我可以给你一个优化建议把mvn dependency:go-offline单独提出来缓存依赖层但这会让 Dockerfile 复杂度上升小项目没必要一开始就上。3.2 Dockerfile 具体内容与逐段说明下面这份 Dockerfile 是我在若依项目中实际跑过的版本直接放到后端工程根目录即可# 第一阶段编译 FROM maven:3.8.6-jdk-8 AS build WORKDIR /build COPY . . RUN mvn clean package -DskipTests -pl ruoyi-admin -am # 第二阶段运行 FROM eclipse-temurin:8-jre ENV TZAsia/Shanghai WORKDIR /app COPY --frombuild /build/ruoyi-admin/target/ruoyi-admin.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -Xms512m, -Xmx1024m, -jar, app.jar]逐个说下关键点。-pl ruoyi-admin -am表示只构建ruoyi-admin模块同时构建它依赖的其他模块最终产出的 jar 就在ruoyi-admin/target下。ENV TZAsia/Shanghai是必须写的。不带时区的话容器默认 UTC若依后台的时间显示会比北京时间晚 8 小时登录日志、操作日志、定时任务调度时间全乱套。在 Debian 系 JRE 镜像里设置这个环境变量就够了不需要再额外拷贝时区文件。-Xms512m -Xmx1024m是最小堆和最大堆大小。若依默认内存占用不算夸张512M 起跑没压力但如果你的服务器内存紧张可以把-Xmx降到 512m。堆大小不是越大越好JVM 如果超出容器可用内存可能导致容器被杀。3.3 构建运行的完整命令Dockerfile 放在后端工程根目录后执行下面两条命令就能完成镜像构建docker build -t ruoyi-backend .如果 Maven 依赖下载特别慢你可以在后端工程根目录的settings.xml或 Maven 全局配置里配置镜像仓库。这里不展开只是提醒依赖拉不下来时别怀疑代码先看 Maven 日志里卡在哪个包。构建完成后可以用docker images看到本地多了一个ruoyi-backend镜像。此时单独运行它一定会报数据库连不上因为没有 MySQL 和 Redis 可连。所以下一步直接用 Compose 把它们编排到一起而不是单独docker run后端。4. 用 Docker Compose 编排整套环境4.1 目录规划与编排文件我为项目建的目录结构是这样的ruoyi-docker/ ├── docker-compose.yml ├── Dockerfile ├── sql/ │ ├── ry_2024xxxx.sql │ └── quartz.sql └── backend/ └── 后端源码或构建上下文实际用的时候把Dockerfile和后端源码放在同一个构建上下文中我习惯把整个后端工程目录作为 Compose 文件所在目录的子目录然后在docker-compose.yml里用build: ./backend指过去。下面是一份可参考的编排文件services: mysql: image: mysql:8.0 container_name: ruoyi-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: ruoyi TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_general_ci ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql - ./sql:/docker-entrypoint-initdb.d healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -proot123456] interval: 5s timeout: 3s retries: 10 redis: image: redis:7.0-alpine container_name: ruoyi-redis restart: unless-stopped command: [redis-server, --requirepass, redis123456, --appendonly, yes] ports: - 6379:6379 volumes: - ./redis-data:/data healthcheck: test: [CMD, redis-cli, -a, redis123456, ping] interval: 5s timeout: 3s retries: 10 backend: build: ./backend container_name: ruoyi-backend restart: unless-stopped depends_on: mysql: condition: service_healthy redis: condition: service_healthy environment: DB_HOST: mysql DB_PORT: 3306 DB_NAME: ruoyi DB_USER: root DB_PASSWORD: root123456 REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: redis123456 ports: - 8080:8080这里需要解释几个容易忽略的设计。MySQL 和 Redis 都映射了宿主机端口理论上生产环境可以不映射数据库端口只让后端容器通过内部网络访问但既然讲的是通用部署方案保留端口能让排错更方便。真正对外提供服务只需要映射 8080 即可。如果担心安全内网环境里建议把3306和6379的宿主机端口映射删掉。MYSQL_DATABASEruoyi表示容器启动时自动创建名为ruoyi的库。后面执行ry_2024xxxx.sql时脚本里通常也有CREATE DATABASE IF NOT EXISTS ruoyi之类的语句二者互不冲突。4.2 健康检查与启动顺序初次布若依后端的人都容易踩同一个坑MySQL 容器刚running后端就开始连数据库结果 MySQL 还没完成初始化后端启动失败。Compose 的depends_on默认只能保证启动顺序不能保证“可服务”必须配合健康检查。healthcheck的作用是让 Docker 每隔几秒探测一次容器状态探活成功后其他容器才会被拉起。我在 MySQL 的探活命令里用mysqladmin ping在 Redis 里用redis-cli ping都是最轻量的方式。retries设为 10间隔 5 秒给数据库初始化留了足够时间。如果不用健康检查还有一种省事的写法是在后端启动脚本里等待但那样代码侵入性太强不值得。Compose 自带机制就能解决不要自己造轮子。4.3 数据库初始化和数据持久化./sql目录挂载到/docker-entrypoint-initdb.d后只有第一次创建 MySQL 数据目录时才会执行初始化脚本。后面重启容器不会重复执行。数据持久化主要通过两个目录./mysql-data对应 MySQL 的数据文件./redis-data对应 Redis 的 AOF 文件。这两个目录一旦生成就包含了真实的业务数据。备份时不要只拷贝 sql 脚本要把整个mysql-data目录打包。恢复时先停掉 MySQL 容器清空或替换mysql-data再重新启动。有两点经验值得记一下第一MySQL 容器以mysql用户运行宿主机上挂载目录的属主如果不是 uid 999可能会因权限问题启动失败直接chown -R 999:999 mysql-data有时比纠结授权更快。第二初始化脚本执行时间较长不要一看到后端报“数据库不存在”就急着删卷重建先看 MySQL 日志是否还停在初始化阶段。5. 启动、验证与前后端联调5.1 一键启动与日志排查所有准备工作完成后执行docker compose up -d --build--build会先构建后端镜像再按下依赖顺序启动三个容器。首次执行耗时较长主要时间花在 Maven 拉依赖和 MySQL 初始化脚本上不要中途按 CtrlC否则未必是失败。启动过程中最常用的两个命令docker compose ps docker compose logs -f backenddocker compose ps能看每个容器当前状态。三个容器都显示running且没有不断重启的迹象后再用日志确认后端是否真正启动完成。5.2 后端健康状态验证若依后端启动成功的标志是日志里出现类似Started RuoYiApplication in xx seconds这样一行。但只凭日志还不够我习惯再打一个接口验证。若依默认开启验证码接口不需要登录就能访问。下列请求如果能返回包含uuid和img字段的 JSON说明 Spring Boot 正常、数据库连接和 Redis 连接都通了curl http://localhost:8080/captchaImage如果返回的是空白页或者直接连接失败分几种情况看docker compose logs -f backend里有异常堆栈重点看是数据库拒绝连接还是 Redis 超时如果完全没有请求日志确认防火墙是否放行了 8080 端口如果在服务器上 curl 通但外部访问不通检查云安全组的入站规则。5.3 前端联调的重点配置若依前后端分离工程里前端开发环境和生产环境的接口地址存在不同位置。Vue2 老版本在vue.config.js里配置转发规则Vue3 版本在vite.config.ts的 server 配置里把/prod-api转发到后端地址。无论哪一版核心思路都一样前端请求统一走/prod-api前缀本地开发时由前端构建工具把该前缀替换成后端实际地址。这里有个高频误区很多人改了后端接口地址后前端页面还在请求 8080 之外的端口或者请求返回 404。正确做法是确认前端配置里的目标地址就是http://后端服务器IP:8080并且确保若依前端路由里请求的 path 与后端 controller 的RequestMapping匹配。还有一点初次登录时如果前端报跨域错误先别急着在后端加全局跨域配置除非你明确知道自己在干什么。若依后端默认对大多数来源做了放行但生产上更推荐由 Nginx 做统一流量转发让前端与后端同源避免在代码里裸奔 CORS。5.4 从部署角度看前端 TS 报错热搜里经常能看到“若依 vue3 ts 报错”。这类问题虽然在前端项目里出现但部署联调时也常常被误判为后端问题。实际踩过以后我总结出三个常见方向。第一是依赖版本问题若依 Vue3 版本的package.json对 Node 版本有要求如果你是 Node 20安装依赖时可能提示engines不满足或node-sass类依赖构建失败建议对照官方 README 使用匹配的 Node 版本推荐 16 或 18。第二是类型编译报错Vue3 TS 工程里偶发的类型推导问题npm run build时才能暴露后端部署正确但前端构建失败时先从tsconfig.json的skipLibCheck和路径别名排查。第三是接口地址与路由 base 拼错前端VITE_APP_BASE_API必须与后端实际 context-paht 对齐若依后端默认没有上下文前缀因此通常是/prod-api加上后端具体路径。6. 部署现场问题清单与排查方法6.1 MySQL 连接失败的几种典型报错后端日志里出现Communications link failure时先从网络连通性排查在 backend 容器里执行ping mysql看能不能解析到 MySQL 容器 IP再确认账号密码是否匹配。若依默认账号是root密码是 Compose 里MYSQL_ROOT_PASSWORD设置的值。如果报错是Public Key Retrieval is not allowed说明 MySQL 8 使用了caching_sha2_password认证而 JDBC 驱动没有启用公钥检索。修改后端application.yml的数据源连接串增加参数url: jdbc:mysql://${DB_HOST:mysql}:${DB_PORT:3306}/${DB_NAME:ruoyi}?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai需要注意serverTimezone必须与容器时区一致否则日期时间字段可能会有偏差。如果你对安全要求较高useSSLfalse会降低连接传输的加密级别内网环境可接受公网环境建议改用其他加密方式。6.2 Redis 连接异常的排查路径若依启动时如果 Redis 连不上日志通常会直接报Unable to connect to Redis或者JedisConnectionException。最常见原因是密码不匹配。很多教程里 Redis 是不设密码的但若依的application.yml默认要求填password字段要么留空要么填上你启动 Redis 时设置的密码。排查步骤建议按这个顺序先看 Compose 里 Redis 的密码是什么再打开后端配置里 Redis 密码是否一致最后进入容器手动验证docker exec -it ruoyi-redis redis-cli -a redis123456 ping如果返回PONGRedis 服务本身没问题问题大概率出在配置或网络。localhost和redis这两个主机名在容器环境里是完全不同的地址后端容器内不能用localhost连接 Redis 容器必须用服务名redis。6.3 Windows 下 Docker Desktop 无法启动Windows 上安装 Docker Desktop 后有时会弹窗报错Docker Desktop failed to start because virtualization support is not detected。这通常不代表系统不支持虚拟化而是 Hyper-V 或“Windows 虚拟机监控程序平台”没启用或者 BIOS 里的 VT-x/AMD-V 被关了。处理路径是先进入 BIOS 开启虚拟化选项再到 Windows“启用或关闭 Windows 功能”里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”装好 WSL2 后执行wsl --set-default-version 2最后重启 Docker Desktop。老机器如果 BIOS 里找不到虚拟化选项也可以尝试 Docker Desktop 的旧版本或改用 WSL 原生方式但这一步属于环境适配和若依本身无关。6.4 从热搜词看若依部署后的高频话题部署完成后我偶尔会在搜索热词里看到一些若依相关的问题和这次的容器化流程有交叉这里一并做个速查热搜场景答案与关联点ruoyi 在哪里写入登录用户的信息登录成功后后端将LoginUser对象写入 Rediskey 通常为login_tokens:加 UUID。请求头带Authorizationtoken后端从 Redis 取对应用户信息若依框架集成 druid 实现数据库密码加密在application.yml中配置 Druid 的publicKey和加密后的密码部署时用环境变量覆盖即可不必把明文写在镜像里若依主子表导出功能后端基于 EasyExcel 动态生成表头部署时只要保证导出目录可写容器内没有特殊依赖若依新加模块在ruoyi-system下新增业务模块重新mvn package生成 jar再docker build覆盖镜像若依 Vue3 去掉验证码验证码开关一般存在数据库配置表sys_config中修改sys.account.captchaEnabled的值为 false重启后端生效这些点如果展开每一个都能单独写一篇文章。部署阶段你只需要知道后端容器本质是“JDK8 jar 环境变量”你改代码后重新构建镜像就是一次标准的迭代发布。7. 写在最后这次把若依后端做成 Docker 部署回头看我个人最大的体会是大部分人第一次失败不是错在 Dockerfile 或 Compose 语法上而是错在没理解容器之间的网络关系。后端容器内看到的主机名是mysql、redis不是localhost这三者之间靠 Compose 内部网络通信数据库密码、Redis 密码、时区、字符集这几类“慢变量”只要有一个不一致排查时间就会成倍上涨。最后再分享一个小技巧正式发布前我会把docker compose config的输出检查一遍这个命令会展开所有配置并做语法校验。然后执行一次docker compose down -v再up -d验证从零开始的完整初始化。这套流程能保证另一台机器上跑起来的行为一致交付后基本不会再被半夜叫起来改环境。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进