版本:v2.0(已实战验证)
创建日期:2026-09-16 | 最后更新:2026-09-16
部署方式:Docker Compose(离线,目标环境无外网)
验证状态:✅ 已于 2026-09-16 在一台 4 GB 内存的 Linux 虚拟机上完整部署成功,
通过http://192.168.21.101/正常访问
零、快速路径
给已熟悉本手册的人。初次部署请从第一章顺序阅读,尤其是第 2 章的角色分工。
完整命令与说明见第四、五、六章。
【构建机】Windows + Docker + 外网
1 | cd E:\project\Java\wms-dt\Backend |
【目标机】Linux + Docker,无外网
需要放到同一目录的文件:docker-compose.yml、wmsue0916.sql、deploy/nginx-container.conf、app-images.tar、base-images.tar
1 | docker load -i base-images.tar |
三个最容易出错的地方
| # | 要点 | 出错后果 |
|---|---|---|
| 1 | 初始化脚本只能用 wmsue0916.sql |
用其他三份会缺表或缺列,功能静默失效(记录 9、10) |
| 2 | 基础镜像只需 mysql:8.4 |
多带 temurin/nginx 会白传约 530 MB |
| 3 | 目标机必须加 --no-build |
目标机无源码,构建会直接失败 |
一、部署架构
1 | ┌────────────────────────────────────────┐ |
为什么采用「离线 jar」方案:目标环境无法访问外网,而原生 Docker 流程有三处依赖联网。
| # | 联网点 | 应对方式 |
|---|---|---|
| 1 | docker build 的 FROM 基础镜像 |
导出基础镜像 tar,目标机 docker load |
| 2 | 镜像内下载 Maven / npm 依赖 | 改为在构建机构建 jar 与 dist,镜像内只 COPY |
| 3 | docker compose up 拉取 mysql:8.4 |
同 1,随基础镜像一起导出 |
第 2 点是本方案的核心:把两个最大的联网点合并到构建机,目标机只负责运行。
二、环境要求
2.1 三种角色的分工
整个流程涉及三种角色。它们可以是同一台机器,也可以分开:
| 角色 | 需要什么 | 当前由谁承担 |
|---|---|---|
| ① 构建产物 | JDK 21 + Maven + Node.js | Windows 开发机 |
| ② 构建镜像 | Docker + 外网 | 同一台 Windows 开发机(已装 Docker) |
| ③ 运行服务 | 仅 Docker,无需外网 | 目标机(离线服务器) |
当前环境 ①② 已合并:开发机同时具备编译工具链与 Docker,
因此第 4 章可在一台机器上一次走完。
若换到没有 Docker 的机器编译,改用「附录 A:无 Docker 时的两机协作方案」。③ 必须与 ①② 分开——目标环境无法访问外网,镜像只能离线导入。
2.2 构建机(需外网 + Docker)
| 项 | 要求 | 当前环境 |
|---|---|---|
| JDK | 21 | ✅ 21.0.8 |
| Maven | 3.9+ | ✅ 3.9.15 |
| Node.js | 20.19+ 或 22.12+ | ✅ v22.19.0 |
| Docker | 20.10+,含 Compose v2 | ✅ CLI 29.8.0 / Compose v5.5.1 |
| 内存 | 建议 8 GB 以上(编译峰值约 3 GB) | — |
| 网络 | Maven 中央仓库、npm registry、Docker Hub | ✅ |
⚠️ Windows 下必须启动 Docker Desktop:只装 CLI 不够。
守护进程未运行时,所有 docker 命令都会报failed to connect to the docker API at npipe:////./pipe/docker_engine。
启动方式与排查见问题记录 5。
2.3 目标机(无外网)
| 项 | 要求 |
|---|---|
| 操作系统 | Linux(Rocky / RHEL / Ubuntu 均可) |
| Docker | 20.10+,含 Compose v2 |
| 内存 | 最低 4 GB(预算见 6.5) |
| 磁盘 | 至少 20 GB(镜像约 1.5 GB + 数据卷) |
| 网络 | 无需外网;需能被浏览器访问到 80 端口 |
2.4 当前测试环境参数
| 项 | 值 |
|---|---|
| 虚拟机 IP | 192.168.21.101 |
| 网络模式 | VMware NAT(VMnet8),仅宿主机可访问 |
| 内存 | 4 GB |
| 访问地址 | http://192.168.21.101/ |
NAT 模式下局域网其他机器访问不到,仅供本机测试。若要多人访问需改为桥接模式。
三、部署物清单
| 文件 | 生成位置 | 大小 | 目标机是否需要 |
|---|---|---|---|
base-images.tar |
构建机 docker pull mysql:8.4 + docker save |
约 600 MB | ✅ 仅首次 |
app-images.tar |
构建机 docker compose build + docker save |
约 830 MB | ✅ 每次发版 |
wmsue0916.sql |
仓库根目录 | 7.21 MB | ✅ 必须用这份 根目录共 4 份快照,仅这份的表与列都与实体一致,详见记录 9、10 |
docker-compose.yml |
仓库根目录 | 小 | ✅ |
deploy/nginx-container.conf |
仓库 | 小 | ✅ |
Backend/、Frontend/ 源码 |
仓库 | — | ❌ 仅构建机需要 |
.env |
— | — | ❌ 当前不需要——口令已直接写入 compose,见 6.3 |
目标机不需要源码、JDK、Node.js、Maven,也不需要
eclipse-temurin与nginx基础镜像(原因见 4.6)。
app-images.tar的体积来自实测:wms-dt/server562 MB +wms-dt/web269 MB。
打包成 tar 后因层压缩通常略小,但同量级。
四、阶段一:构建产物与镜像(构建机)
本章在同一台具备「编译工具链 + Docker + 外网」的机器上完成,即 2.1 中的角色 ①②。
若该机没有 Docker,改用「附录 A:无 Docker 时的两机协作方案」。
工作目录约定:本仓库的 Maven 父工程在
Backend/下,**项目根目录没有pom.xml**。
因此所有 Maven 命令都必须先cd Backend,否则会报there is no POM in this directory。
下文各命令已包含cd,请从项目根目录依次执行。⚠️ PowerShell 参数陷阱:在 Windows PowerShell 下,带点的
-D参数会被拆开
——-Dmaven.test.skip=true会被解析成-Dmaven和.test.skip=true两个参数,
Maven 报Unknown lifecycle phase ".test.skip=true"。
两种解法任选其一:
写法 效果 mvn clean package -DskipTests推荐。参数不含点,无需引号;编译测试代码但不执行 mvn clean package "-Dmaven.test.skip=true"加引号即可;连测试代码也不编译,略快 下文命令统一采用
-DskipTests写法。在 Linux/macOS 的 bash 下两种写法均可,无此问题。
4.1 构建后端 jar
1 | cd Backend |
为什么必须跳过测试:6 个测试类全部标注
@SpringBootTest,会启动完整 Spring 容器
并连接 MySQL。打包机(开发本机)通常没有运行数据库,不跳过会在 test 阶段失败。
集成测试应在 CI 环境(有数据库)执行,不纳入打包流程。
4.2 构建前端 dist
1 | cd ..\Frontend |
npm run build内含vue-tsc类型检查,类型错误会直接让构建失败。
4.3 构建应用镜像
1 | cd .. # 回到项目根目录 E:\project\Java\wms-dt |
本步骤没有任何编译动作,只把 4.1 产出的 jar 与 4.2 产出的 dist
COPY进镜像,
通常 1~2 分钟完成。首次执行需拉取基础镜像(eclipse-temurin:21-jre、nginx:1.27-alpine),因此构建机需要外网。
内存紧张时可分开串行构建:
1 | docker compose build wms-server |
4.4 本机验证(建议)
送入目标机之前先在本机确认镜像可用,避免把问题带进离线环境:
1 | docker compose up -d --no-build |
浏览器打开 http://localhost/ 应能看到前端页面。确认无误后清理:
1 | docker compose down # 保留数据卷;要连数据一起清掉用 down -v |
4.5 导出应用镜像
1 | docker save -o app-images.tar wms-dt/server:1.0.0 wms-dt/web:1.0.0 |
4.6 准备并导出基础镜像(仅首次需要)
1 | docker pull mysql:8.4 |
只需要
mysql:8.4,不要带eclipse-temurin:21-jre与nginx:1.27-alpine。那两者是构建期的基础镜像(Dockerfile 里的
FROM),它们的层已经包含在wms-dt/server与wms-dt/web之中——Docker 镜像是自包含的层堆栈,docker save会把所有层一起导出。目标机载入应用镜像后即可直接运行,
不需要再单独载入这两个基础镜像。实测体积对比(2026-09-16):
方案 内容 体积 ❌ 带全部三个 temurin 459 MB + nginx 74.5 MB + mysql 约 600 MB 约 1.1 GB ✅ 只带 mysql mysql 约 600 MB 约 600 MB 基础镜像只在首次部署时准备,后续发版只更新
app-images.tar。
五、阶段二:传输到目标机
1 | # 网络可达时,直接传 |
或先打成一个包再传:
1 | tar -czf wms-dt-offline.tar.gz ` |
完全物理隔离时,用 U 盘或内网文件服务器中转。
目标机与构建机是同一台时(如当前测试环境),本章可整章跳过,直接从第 6 章继续。
六、阶段三:目标机部署
6.1 解包
1 | mkdir -p /opt/wms-dt && cd /opt/wms-dt |
6.2 载入镜像
1 | docker load -i base-images.tar # 首次部署必须,约 450 MB |
6.3 配置数据库口令
当前 docker-compose.yml 已直接写入口令,无需 .env,本节可跳过。
现行取值:
| 配置项 | 值 | 说明 |
|---|---|---|
MYSQL_DATABASE |
wmsue |
必须与 wmsue0916.sql 中建库名一致 |
MYSQL_USER |
wms |
应用专用账号,不用 root |
MYSQL_PASSWORD |
WmsApp2026 |
应用连接用 |
MYSQL_ROOT_PASSWORD |
WmsRoot2026 |
仅供容器内运维排查 |
后端 DB_USER / DB_PASSWORD |
与上面一致 | 两处必须同步修改 |
| 对外端口 | 80 |
被占用时改成如 "8080:80" |
mem_limit |
1024m / 1792m / 128m | 见 6.5 |
⚠️ 这是测试环境的取舍:
docker-compose.yml是入库文件,明文口令会进入 Git 仓库。
正式部署前建议改回环境变量注入:
- 把 compose 中 5 处口令改回
${MYSQL_XXX}形式cp .env.example .env并填入真实口令(.env已被.gitignore忽略)改口令时务必同步修改两处:
mysql服务的MYSQL_PASSWORD与wms-server
服务的DB_PASSWORD。只改一处会导致后端连不上库。口令请用纯字母数字:compose 会对值做变量插值,
$会被当成变量引用、#之后会被当成注释。
6.4 启动
1 | docker compose up -d --no-build |
--no-build是必须的:目标机上没有源码,若 compose 尝试构建会直接失败。
启动前建议先做一次配置自检——若输出中没有任何 WARN ... variable is not set,
说明变量都已就位:
1 | docker compose config --quiet |
等待 MySQL 初始化(要导入 3.88 MB 数据,约 1~2 分钟):
1 | docker compose ps # 等到 mysql 显示 (healthy) |
日志出现下面两行即为成功:
1 | Tomcat started on port 8080 (http) |
6.5 内存预算(4 GB 环境)
| 组件 | compose 上限 | 实际预期 |
|---|---|---|
| 系统 + Docker daemon | 不可压缩 | 约 1.1 GB |
mysql |
1024m | 约 550 MB |
wms-server |
1792m | 约 1.45 GB |
wms-web |
128m | 约 30 MB |
| 合计 | 2944m | 约 3.1 GB |
为降低 MySQL 占用,compose 中已加三项启动参数:
1 | --performance-schema=OFF MySQL 8 中固定占用数百 MB,测试环境无监控需求 |
后端 MaxRAMPercentage=70 而非更高:该参数只约束堆,堆外还需元空间(约 120 MB)、
线程栈(Tomcat 200 线程 × 1 MB)、代码缓存与直接内存,合计 400~500 MB。
七、验证清单
按顺序逐项确认,任何一项不过就不要往下走。
| # | 检查项 | 命令 / 方式 | 期望结果 |
|---|---|---|---|
| 1 | 容器状态 | docker compose ps |
mysql healthy,另两个 running |
| 2 | 数据库表 | docker compose exec mysql mysql -uwms -p wmsue -e "SHOW TABLES;" |
10 张表 |
| 3 | 后端直连 | docker compose exec wms-server curl -s localhost:8080/api/wms/inventory-stats |
{"code":200,...} |
| 4 | Nginx 反代 | curl -s http://localhost/api/wms/inventory-stats |
与第 3 步一致 |
| 5 | 前端首页 | 浏览器 http://192.168.21.101/ |
Dashboard 正常显示 |
| 6 | 深链接回退 | 浏览器 http://192.168.21.101/inventory |
正常显示,非 404 |
| 7 | 3D 场景 | F12 → Network | WarehouseTwin.swapp 加载成功(67 MB,首次较慢) |
| 8 | WebSocket | F12 → Network → WS | 101 Switching Protocols |
| 9 | 心跳 | 同上,看消息帧 | 每 30 秒一条 {"type":"heartbeat"} |
| 10 | 建连首包 | 同上 | inventoryLayoutResult,约 1.37 MB |
第 8~10 项是本系统的核心,必须实测——前面几步只能证明 HTTP 通了。
八、版本升级
目标机不需要重新载入基础镜像,只替换应用镜像。
1 | # 【构建机】 |
镜像 tag 应与 Git tag 对齐:Git 打 v1.0.0,镜像同时打 1.0.0。
出问题时能立刻定位线上跑的是哪份代码;回滚就是改 compose 里的 tag 再 up -d。
8.1 什么时候需要重新 build
docker compose build 与 docker compose up 消费的是不同的东西。
改错了地方会白等一次构建,或者误以为”改完没生效”:
| 改动内容 | 需要重新 build? | 原因 |
|---|---|---|
| 后端 Java 代码 | ✅ 需要 (先 mvn package 再 build) |
jar 变了,而 jar 被 COPY 进镜像 |
| 前端 Vue / TS 代码 | ✅ 需要 (先 npm run build 再 build) |
dist 变了,而 dist 被 COPY 进镜像 |
Backend/Dockerfile |
✅ 需要 | 镜像定义变了 |
Frontend/Dockerfile |
✅ 需要 | 镜像定义变了 |
deploy/nginx-container.conf |
✅ 需要 | 它被 COPY 进 web 镜像 |
任一处 .dockerignore |
✅ 需要 | 影响构建上下文的内容 |
docker-compose.yml |
❌ 不需要 | 只含运行期配置(环境变量、内存上限、端口映射),由 up 读取,不进镜像 |
wmsue0916.sql |
❌ 不需要 | 通过 volume 挂载进 MySQL 容器 |
docs/ 下的文档 |
❌ 不需要 | — |
判断方法——比较镜像创建时间与各构建输入的修改时间:
1 | docker images --filter "reference=wms-dt/*" --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" |
只要上表标「需要」的那几项,修改时间都早于镜像创建时间,镜像就是最新的,不必重建。
补充:
docker compose up -d本身不会触发构建——只要本地已有对应 tag 的镜像
就会直接复用。只有显式加--build才会强制重建。实测记录(2026-09-16):镜像创建于 18:45~18:46,此后只改了
docker-compose.yml
与文档,因此up -d直接复用镜像,无需重新构建。
九、常见问题速查
| 现象 | 原因 | 处理 |
|---|---|---|
docker compose up 卡住不动 / 超时 |
在尝试联网拉取镜像 | 加 --no-build;确认 docker images 里基础镜像都在 |
pull access denied for eclipse-temurin |
基础镜像未载入 | docker load -i base-images.tar |
failed to solve: ... context: ./Backend |
目标机无源码,compose 仍在尝试构建 | 用 --no-build;仍报错则注释掉 compose 里的两个 build: 段 |
COPY failed: no source files were specified |
构建机上 jar 或 dist 未生成 | 先执行 4.1、4.2 两步 |
| 启动即退出,提示”配置项未被解析” | .env 未创建或变量未填 |
docker compose exec wms-server env | grep DB_ |
Access denied for user |
MySQL 账号或 host 不匹配 | 账号须为 'wms'@'%'(容器内网访问) |
| 中文乱码 / 时间差 8 小时 | JDBC URL 参数缺失 | 确认 characterEncoding=UTF-8 与 serverTimezone=Asia/Shanghai |
| WebSocket 握手返回 400 | Nginx 缺 Upgrade/Connection 头 |
对照 deploy/nginx-container.conf 检查 |
| WebSocket 60 秒断一次 | proxy_read_timeout 未生效 |
确认 /ws/ 用 ^~ 前缀匹配,未被正则 location 截走 |
| 页面刷新 404 | 缺 try_files ... /index.html |
前端是 history 路由,必须回退到 index.html |
容器被 OOM Killer 杀掉(exit code 137) |
内存超限 | 调 .env 中的 SERVER_MEM_LIMIT,或加 swap,或换更大内存的机器 |
改 wmsue0916.sql 后重新 up 不生效 |
/docker-entrypoint-initdb.d 仅在数据目录为空时执行 |
docker compose down -v 清空数据卷重来(会丢数据) |
| 应用能启动、部分接口正常,但对账/复盘报「表不存在」 | 初始化脚本用错了——wmsue.sql 只有 7 张表 |
改用 wmsue0916.sql,见记录 9 |
某接口 HTTP 500,日志报 Unknown column 'xxx' |
表的列与实体不一致(表少一列) | wmsue0912.sql 缺 skuName,改用 wmsue0916.sql,见记录 10 |
十、实际问题记录
本章用于记录实际部署中遇到的问题与解决办法,按发生顺序追加。
记录格式:现象 → 原因 → 解决 → 是否已回写文档。
记录模板
1 | #### [日期] 问题简述 |
记录 2:PowerShell 下 -Dmaven.test.skip=true 被拆成两个参数
发生时间:2026-09-16 17:16
现象:
cd Backend后执行mvn clean package -Dmaven.test.skip=true,Maven 在
第一个模块(父 pom)就失败,耗时不足 1 秒完整报错:
1
2[ERROR] Unknown lifecycle phase ".test.skip=true". You must specify a valid
lifecycle phase or a goal in the format <plugin-prefix>:<goal> ...原因:Windows PowerShell 的参数解析规则——
-Dmaven.test.skip=true中含有
多个.,PowerShell 会把它拆成-Dmaven与.test.skip=true两个独立参数传入。
Maven 把后者当成了生命周期阶段名,因此报「未知的生命周期阶段」。
参数本身、pom 配置、代码都没有问题。解决:改用不含点的等价参数,或给参数加引号:
1
2mvn clean package -DskipTests # 推荐,无需引号
mvn clean package "-Dmaven.test.skip=true" # 加引号也可以两者区别:
-DskipTests仍编译测试代码但不执行;-Dmaven.test.skip=true连编译也跳过。
本项目的测试类编译无问题(失败只发生在运行时连库),故-DskipTests足够。影响范围:所有在 PowerShell 下带点的
-D参数都有此问题,例如-Dspring.profiles.active=prod、-Dmaven.test.failure.ignore=true等,
一律需要加引号。Linux/macOS 的 bash 无此问题。已回写:本手册第 4 章开头的「PowerShell 参数陷阱」说明;4.1、4.2 命令已改为
PowerShell 写法;本章记录 2。
记录 4:PowerShell 5.1 执行含中文的 .ps1 报语法错误
发生时间:2026-09-16 17:3x
现象:执行
powershell -ExecutionPolicy Bypass -File deploy\prepare-image-bundle.ps1,
报多处语法错误,且报错位置(如第 120 行的&&、第 50 行的})看起来毫无道理
——&&明明在单引号字符串内部完整报错:
1
2
3
4
5
6
7
8At ...prepare-image-bundle.ps1:50 char:1
+ }
+ ~
Unexpected token '}' in expression or statement.
At ...prepare-image-bundle.ps1:120 char:50
+ Write-Host ' mkdir -p /root/wms-image-build && cd /root/wms-imag ...
+ ~~
The token '&&' is not a valid statement separator in this version.原因:Windows PowerShell 5.1 读取无 BOM 的 UTF-8 脚本文件时,会按系统 ANSI
代码页(中文 Windows 为 GBK)解析。脚本中的中文字符串被错误解码后,
字节序列里出现了被当作引号或语句分隔符的字符,导致引号配对错位,
于是解析器从某一行开始整体错乱——报错行号与实际问题行完全无关。
PowerShell 7(pwsh)默认按 UTF-8 读取,无此问题。解决:给脚本文件加 UTF-8 BOM(3 字节
EF BB BF),5.1 与 7 均可正确识别:1
2
3$p = 'deploy\prepare-image-bundle.ps1'
$c = [System.IO.File]::ReadAllText($p, (New-Object System.Text.UTF8Encoding($false)))
[System.IO.File]::WriteAllText($p, $c, (New-Object System.Text.UTF8Encoding($true)))加 BOM 后重新解析,无任何语法错误。
通用结论:本仓库中任何含中文的
.ps1都必须带 UTF-8 BOM。
Markdown、Java、properties 等文件不受影响(各有各的读取方式)。⚠️ 后续补充(2026-09-16):编辑工具会剥离 BOM。本次修改
prepare-image-bundle.ps1的内容后,BOM 丢失、文件立刻报出与本节完全相同的
语法错误。因此每次编辑含中文的.ps1之后,都必须重新检查并补回 BOM:1
2
3
4
5
6$p = 'deploy\prepare-image-bundle.ps1'
if ([System.IO.File]::ReadAllBytes($p)[0] -ne 239) {
$c = [System.IO.File]::ReadAllText($p, (New-Object System.Text.UTF8Encoding($false)))
[System.IO.File]::WriteAllText($p, $c, (New-Object System.Text.UTF8Encoding($true)))
Write-Host "BOM 已补回"
}已回写:
deploy/prepare-image-bundle.ps1已带 BOM;附录 B 的维护提示;
本章记录 4。
记录 6:直连 Docker Hub 超时,拉不到基础镜像
发生时间:2026-09-16 18:0x
现象:
docker compose build在解析基础镜像元数据阶段即失败,两个服务同时报错完整报错:
1
2
3
4
5
6
7ERROR [wms-web internal] load metadata for docker.io/library/nginx:1.27-alpine
ERROR [wms-server internal] load metadata for docker.io/library/eclipse-temurin:21-jre
failed to resolve source metadata for docker.io/library/nginx:1.27-alpine:
failed to do request: Head "https://registry-1.docker.io/v2/library/nginx/manifests/1.27-alpine":
dialing registry-1.docker.io:443 ... connectex: A connection attempt failed because
the connected party did not properly respond after a period of time原因:国内网络无法直连 Docker Hub(
registry-1.docker.io)。这不是配置错误,
也不是 Dockerfile 的问题——在FROM解析阶段就失败了,源码根本没参与。解决途径(按推荐顺序):
- 配置镜像加速器(最可靠):登录阿里云控制台 → 容器镜像服务 ACR →
镜像工具 → 镜像加速器,取专属地址,填入 Docker Desktop 的
Settings → Docker Engine 的registry-mirrors,Apply & Restart - 公共镜像源 + 手动打标:
docker pull <镜像源>/library/nginx:1.27-alpine
后docker tag回标准名。适合临时应急,不改 daemon 配置 - **从能上外网的机器
docker save后docker load**:绕开所有镜像源问题,
且产出的base-images.tar本来就是离线部署需要的文件
- 配置镜像加速器(最可靠):登录阿里云控制台 → 容器镜像服务 ACR →
注意:第三方免费公共节点(如
docker.xuanyuan.me)限流严重,
实测报「免费节点当前繁忙」并引流至付费版,不可作为唯一方案。
另:网易hub-mirror.c.163.com早已停服,配置里若还留着应删除。
公共源状态变化很快,建议用固定账号的专属加速器。已回写:本章记录 6。
记录 7:useradd 报 UID 1000 is not unique
发生时间:2026-09-16 18:1x
现象:镜像源问题解决后,
wms-server构建在第 3/5 步失败(前端wms-web正常)完整报错:
1
2
3> [wms-server 3/5] RUN useradd -r -u 1000 -s /sbin/nologin wms:
0.203 useradd: UID 1000 is not unique
Dockerfile:27原因:
eclipse-temurin:21-jre基于 Ubuntu,而 Ubuntu 基础镜像自带一个
UID 1000 的用户。Dockerfile 中硬编码了-u 1000,与之冲突。
实测确认:1
2$ docker run --rm eclipse-temurin:21-jre id 1000
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),...解决:**去掉
-u 1000**,改用-r让系统从系统账号区间自动挑选:1
RUN useradd -r -s /sbin/nologin wms
实测结果:
uid=999(wms) gid=999(wms),无冲突。结论:在基础镜像上创建用户时不要硬编码 UID——不同基础镜像的 UID 占用情况
各不相同(Alpine 无 UID 1000 用户、Ubuntu 有),硬编码必然在某类镜像上翻车。已回写:
Backend/Dockerfile已改为useradd -r并加注释说明原因;本章记录 7。
记录 8:漏建 .env 导致 MySQL 无限重启
发生时间:2026-09-16 19:0x
现象:
docker compose up -d --no-build输出 8 条WARN ... variable is not set,
最终以dependency failed to start: container wms-mysql is unhealthy结束;wms-mysql处于Restarting (1)无限重启状态(因 compose 配了restart: unless-stopped)完整报错(藏在容器日志里,
compose up的输出看不到):1
2
3
4
5
6$ docker logs wms-mysql
[ERROR] [Entrypoint]: Database is uninitialized and password option is not specified
You need to specify one of the following as an environment variable:
- MYSQL_ROOT_PASSWORD
- MYSQL_ALLOW_EMPTY_PASSWORD
- MYSQL_RANDOM_ROOT_PASSWORD原因:**
.env文件不存在。compose 会对${MYSQL_ROOT_PASSWORD}做插值,
变量找不到时不报错、而是替换为空字符串**,于是 MySQL 拿到一个空的 root 口令,
而官方镜像要求必须显式提供口令三者之一,因此拒绝初始化并退出。排查要点:
1
2
3docker compose config --quiet # 有 WARN 说明变量没配上
docker compose ps -a # 看容器是否在 Restarting
docker logs wms-mysql # 真正的错误在这里,不在 compose up 的输出里解决(二选一):
- **建
.env**:cp .env.example .env后填入口令 - 直接写入 compose(当前采用):把 compose 中 5 处
${MYSQL_XXX}改为明文值
- **建
注意:改完口令后要
docker compose down再up。若mysql-data卷已被
按旧口令初始化过,新口令不会生效,需docker compose down -v清空数据卷重来。
本次因从未初始化成功,卷是空的,直接重来即可。教训:**
.env不在 Git 里、不会随任何部署包传输,每个新环境都必须手动创建**。
漏建的后果是容器无限重启,而compose up仅显示一句含糊的dependency failed to start,真正的错误只在docker logs里。已回写:6.3 节改为直接写入口令并说明取舍;6.4 节补充
docker compose config --quiet
自检步骤;本章记录 8。
部署验证记录
2026-09-16 首次完整部署,结果:成功。
| 项 | 值 |
|---|---|
| 目标机 | Linux 虚拟机,192.168.21.101,VMware NAT 网络(仅宿主机可访问) |
| 内存 | 4 GB |
| 访问地址 | http://192.168.21.101/ ✅ 正常访问 |
| 部署方式 | 离线:镜像在 Windows 构建机产出 → docker save → MobaXterm 拖入虚拟机 → docker load |
验证结果一览
| # | 事项 | 状态 |
|---|---|---|
| 1 | 三个镜像能否成功构建(docker compose build) |
✅ 通过 实测 wms-dt/server:1.0.0 562 MB、wms-dt/web:1.0.0 269 MB(构建机口径) |
| 2 | docker save / docker load 后镜像能否正常启动 |
✅ 通过 虚拟机侧镜像 ID 与构建机完全一致: server=9d74cc36b193、web=665e19abdbe8 |
| 3 | 4 GB 内存下三容器并行的实际表现 | ✅ 通过(三容器同时运行并对外提供服务) |
| 4 | Nginx 容器能否正确反代到 wms-server 服务名 |
✅ 通过(浏览器经 80 端口访问成功) |
| 5 | WebSocket 经容器内 Nginx 的握手与心跳 | ⬜ 仍待验证——需在浏览器 F12 中确认,见第七章第 8~10 项 |
| 6 | 目标机 docker load base-images.tar 后 mysql 能否正常初始化 |
✅ 通过(后端成功连库并启动) |
| 7 | 数据库表结构是否与实体完全一致(表数 + 列) | ✅ 通过 对 wmsue0916.sql 逐一比对 10 张表的实体与 DDL,无一张缺少实体所需列 |
| 8 | 对账接口 GET /api/wms/reconciliation/diffs 是否可用 |
⬜ 仍待验证——该接口曾因缺列返回 500(记录 10),建议换用 wmsue0916.sql 后复测一次 |
仍待验证的第 5、8 项都与对账/实时推送有关,属于本项目最核心也最容易出问题的部分,
建议在浏览器中实测一次:F12 → Network → WS,确认状态为 101 Switching Protocols、
且每 30 秒收到一条{"type":"heartbeat"}。
附录 A:无 Docker 时的两机协作方案
当构建机没有 Docker(例如早先的 Windows 开发机)、而另有一台带 Docker 与外网的
Linux 机器时,可把第 4 章拆成两段:本机只出产物,镜像交给另一台机器构建。
A.1 在本机组装构建包
1 | # 在项目根目录 E:\project\Java\wms-dt,且已完成 4.1 jar 与 4.2 dist 的构建 |
脚本会自行完成:
- 校验 jar、dist、引擎场景包(
WarehouseTwin.swapp)是否齐备 - 收集 8 项文件到暂存目录
..\wms-image-bundle-stage - 打包为
..\wms-image-bundle.tar.gz(约 105 MB) - 校验包内关键路径是否完整
输出示例:
1 | [1/4] 检查构建产物... |
构建包与「离线部署包」的区别:构建包里是 jar + dist + 镜像构建配置,
用于在另一台机器上docker compose build;离线部署包里是镜像 tar,
用于在目标机上docker load。二者不要混淆。
A.2 在带 Docker 的机器上构建并导出
1 | scp E:\project\Java\wms-image-bundle.tar.gz root@<该机器IP>:/root/ |
随后从第 4.6 节继续(准备基础镜像),再到第 5 章传输、第 6 章部署。