手头一个内部管理系统是基于若依前后端分离版开发的,交付准备阶段客户只给了台全新测试机,要求把后端、数据库、缓存一次性跑起来。当时临时去查各种教程,断断续续折腾了两天,后来把整个流程整理成一份可直接复用的部署方案。这篇指南就把这套方案完整写出来,涉及镜像选型、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-mysql | mysql:8.0 | 存储全部业务表、菜单权限、定时任务配置 |
| ruoyi-redis | redis:7.0-alpine | 缓存登录用户信息、验证码、session |
| ruoyi-backend | 基于 JDK8 自定义镜像 | 运行 ruoyi-admin.jar,提供 REST API |
我没有选择把 MySQL 和 Redis 部署在宿主机上,而是统一容器化,原因有三个:一是交付环境是台干净测试机,宿主机不一定预装数据库;二是 MySQL 数据目录和 Redis 数据目录都能用 volume 持久化,后续换机器直接挂上旧数据就能恢复;三是整个依赖关系可以用 Compose 的depends_on和健康检查控制启动顺序,避免后端容器起来时数据库还没初始化好。
1.3 环境准备与项目初始状态
动手之前先确认几件事。第一,Docker 版本建议不低于 20.10,Compose 插件最好用 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-server=utf8mb4和--collation-server=utf8mb4_general_ci,这部分我会在后面的编排文件里直接给出完整写法。
还有一点:MySQL 8.0 默认的认证插件是caching_sha2_password,如果你的后端 JDBC 驱动版本比较老,会出现 “Public Key Retrieval is not allowed” 这类报错。这个问题我在第 6 节会专门展开,先记住一个思路:在 JDBC 连接串上增加allowPublicKeyRetrieval=true&useSSL=false,同时依赖若依自带的 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 TZ=Asia/Shanghai WORKDIR /app COPY --from=build /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 TZ=Asia/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-server=utf8mb4 - --collation-server=utf8mb4_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_DATABASE=ruoyi表示容器启动时自动创建名为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 初始化脚本上,不要中途按 Ctrl+C,否则未必是失败。
启动过程中最常用的两个命令:
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}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai需要注意:serverTimezone必须与容器时区一致,否则日期时间字段可能会有偏差。如果你对安全要求较高,useSSL=false会降低连接传输的加密级别,内网环境可接受,公网环境建议改用其他加密方式。
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如果返回PONG,Redis 服务本身没问题,问题大概率出在配置或网络。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对象写入 Redis,key 通常为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,验证从零开始的完整初始化。这套流程能保证另一台机器上跑起来的行为一致,交付后基本不会再被半夜叫起来改环境。