docker部署系统手册

版本: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
2
3
4
5
6
7
8
9
10
cd E:\project\Java\wms-dt\Backend
mvn clean package -DskipTests
cd ..\Frontend
npm ci
npm run build
cd ..
docker compose build
docker pull mysql:8.4
docker save -o app-images.tar wms-dt/server:1.0.0 wms-dt/web:1.0.0
docker save -o base-images.tar mysql:8.4

【目标机】Linux + Docker,无外网

需要放到同一目录的文件:docker-compose.ymlwmsue0916.sqldeploy/nginx-container.conf
app-images.tarbase-images.tar

1
2
3
4
docker load -i base-images.tar
docker load -i app-images.tar
docker compose up -d --no-build
docker compose ps

三个最容易出错的地方

# 要点 出错后果
1 初始化脚本只能用 wmsue0916.sql 用其他三份会缺表或缺列,功能静默失效(记录 9、10)
2 基础镜像只需 mysql:8.4 多带 temurin/nginx 会白传约 530 MB
3 目标机必须加 --no-build 目标机无源码,构建会直接失败

一、部署架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
                     ┌────────────────────────────────────────┐
浏览器 ────:80──────▶│ wms-web (nginx:1.27-alpine) │
│ ├─ / 前端静态资源(含引擎) │
│ ├─ /api/ ──▶ wms-server:8080
│ └─ /ws/ ──▶ wms-server:8080
└───────────────┬────────────────────────┘
│ 内部网络 wms-net
┌───────────────▼────────────────────────┐
│ wms-server (eclipse-temurin:21-jre) │
│ profile = prod │
└───────────────┬────────────────────────┘

┌───────────────▼────────────────────────┐
│ wms-mysql (mysql:8.4) │
│ 数据持久化在 named volume │
└────────────────────────────────────────┘

只有 :80 对外暴露,80803306 仅在 Docker 内部网络可达

为什么采用「离线 jar」方案:目标环境无法访问外网,而原生 Docker 流程有三处依赖联网。

# 联网点 应对方式
1 docker buildFROM 基础镜像 导出基础镜像 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/server 562 MB + wms-dt/web 269 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
2
3
cd Backend
mvn clean package -DskipTests
# 产物:Backend/wms-server/target/wms-server-0.0.1-SNAPSHOT.jar(约 27 MB)

为什么必须跳过测试:6 个测试类全部标注 @SpringBootTest,会启动完整 Spring 容器
并连接 MySQL。打包机(开发本机)通常没有运行数据库,不跳过会在 test 阶段失败。
集成测试应在 CI 环境(有数据库)执行,不纳入打包流程。

4.2 构建前端 dist

1
2
3
4
cd ..\Frontend
npm ci
npm run build
# 产物:Frontend/dist/(约 105 MB,含引擎资源)

npm run build 内含 vue-tsc 类型检查,类型错误会直接让构建失败。

4.3 构建应用镜像

1
2
cd ..    # 回到项目根目录 E:\project\Java\wms-dt
docker compose build

本步骤没有任何编译动作,只把 4.1 产出的 jar 与 4.2 产出的 dist COPY 进镜像,
通常 1~2 分钟完成。首次执行需拉取基础镜像(eclipse-temurin:21-jre
nginx:1.27-alpine),因此构建机需要外网。

内存紧张时可分开串行构建:

1
2
docker compose build wms-server
docker compose build wms-web

4.4 本机验证(建议)

送入目标机之前先在本机确认镜像可用,避免把问题带进离线环境:

1
2
3
4
docker compose up -d --no-build
docker compose ps # 等 mysql 显示 healthy
curl.exe -s http://localhost/api/wms/inventory-stats
docker images | Select-String -Pattern "wms-dt|temurin|nginx"

浏览器打开 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
2
docker pull mysql:8.4
docker save -o base-images.tar mysql:8.4

只需要 mysql:8.4,不要带 eclipse-temurin:21-jrenginx:1.27-alpine

那两者是构建期的基础镜像(Dockerfile 里的 FROM),它们的层已经包含在
wms-dt/serverwms-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
2
3
# 网络可达时,直接传
scp base-images.tar app-images.tar docker-compose.yml wmsue0916.sql root@<目标机IP>:/root/
scp -r deploy root@<目标机IP>:/root/

或先打成一个包再传:

1
2
3
4
5
tar -czf wms-dt-offline.tar.gz `
base-images.tar app-images.tar `
docker-compose.yml wmsue0916.sql deploy/nginx-container.conf

scp wms-dt-offline.tar.gz root@<目标机IP>:/root/

完全物理隔离时,用 U 盘或内网文件服务器中转。

目标机与构建机是同一台时(如当前测试环境),本章可整章跳过,直接从第 6 章继续。


六、阶段三:目标机部署

6.1 解包

1
2
3
4
mkdir -p /opt/wms-dt && cd /opt/wms-dt
tar -xzf /root/wms-dt-offline.tar.gz

ls # 应看到 base-images.tar / app-images.tar / docker-compose.yml / wmsue0916.sql / deploy/

6.2 载入镜像

1
2
3
4
5
docker load -i base-images.tar      # 首次部署必须,约 450 MB
docker load -i app-images.tar

# 确认三个基础镜像 + 两个应用镜像都在本地
docker images | grep -E 'wms-dt|mysql|temurin|nginx'

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 仓库。
正式部署前建议改回环境变量注入:

  1. 把 compose 中 5 处口令改回 ${MYSQL_XXX} 形式
  2. cp .env.example .env 并填入真实口令(.env 已被 .gitignore 忽略)

改口令时务必同步修改两处mysql 服务的 MYSQL_PASSWORDwms-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
2
docker compose ps              # 等到 mysql 显示 (healthy)
docker compose logs -f wms-server

日志出现下面两行即为成功:

1
2
Tomcat started on port 8080 (http)
Started WmsServerApplication in X.XXX seconds

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
2
3
--performance-schema=OFF          MySQL 8 中固定占用数百 MB,测试环境无监控需求
--innodb-buffer-pool-size=256M 默认 128M 偏小,调大又会挤占 JVM
--max-connections=50 默认 151,每个连接都要线程栈与缓冲区

后端 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
2
3
4
5
6
7
8
9
10
# 【构建机】
git pull
cd Backend && mvn clean package -DskipTests && cd ..
cd Frontend && npm ci && npm run build && cd ..
docker compose build
docker save -o app-images.tar wms-dt/server:1.0.0 wms-dt/web:1.0.0

# 【目标机】
docker load -i app-images.tar
docker compose up -d --no-build

镜像 tag 应与 Git tag 对齐:Git 打 v1.0.0,镜像同时打 1.0.0
出问题时能立刻定位线上跑的是哪份代码;回滚就是改 compose 里的 tag 再 up -d

8.1 什么时候需要重新 build

docker compose builddocker 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
2
3
4
docker images --filter "reference=wms-dt/*" --format "{{.Repository}}:{{.Tag}}  {{.CreatedAt}}"

Get-ChildItem Backend\Dockerfile, Frontend\Dockerfile, deploy\nginx-container.conf, .dockerignore |
Select-Object LastWriteTime, Name

只要上表标「需要」的那几项,修改时间都早于镜像创建时间,镜像就是最新的,不必重建。

补充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-8serverTimezone=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.sqlskuName,改用 wmsue0916.sql,见记录 10

十、实际问题记录

本章用于记录实际部署中遇到的问题与解决办法,按发生顺序追加。
记录格式:现象 → 原因 → 解决 → 是否已回写文档。

记录模板

1
2
3
4
5
6
7
#### [日期] 问题简述

- **现象**
- **完整报错**
- **原因**
- **解决**
- **已回写**:本手册第 X 章 / compose 注释 / Dockerfile 注释

记录 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
    2
    mvn 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
    8
    At ...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
    7
    ERROR [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 Hubregistry-1.docker.io)。这不是配置错误,
    也不是 Dockerfile 的问题——在 FROM 解析阶段就失败了,源码根本没参与。

  • 解决途径(按推荐顺序):

    1. 配置镜像加速器(最可靠):登录阿里云控制台 → 容器镜像服务 ACR →
      镜像工具 → 镜像加速器,取专属地址,填入 Docker Desktop 的
      Settings → Docker Engine 的 registry-mirrors,Apply & Restart
    2. 公共镜像源 + 手动打标docker pull <镜像源>/library/nginx:1.27-alpine
      docker tag 回标准名。适合临时应急,不改 daemon 配置
    3. **从能上外网的机器 docker savedocker load**:绕开所有镜像源问题,
      且产出的 base-images.tar 本来就是离线部署需要的文件
  • 注意:第三方免费公共节点(如 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
    3
    docker compose config --quiet   # 有 WARN 说明变量没配上
    docker compose ps -a # 看容器是否在 Restarting
    docker logs wms-mysql # 真正的错误在这里,不在 compose up 的输出里
  • 解决(二选一):

    1. **建 .env**:cp .env.example .env 后填入口令
    2. 直接写入 compose(当前采用):把 compose 中 5 处 ${MYSQL_XXX} 改为明文值
  • 注意:改完口令后要 docker compose downup。若 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=9d74cc36b193web=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
2
# 在项目根目录 E:\project\Java\wms-dt,且已完成 4.1 jar 与 4.2 dist 的构建
powershell -ExecutionPolicy Bypass -File deploy\prepare-image-bundle.ps1

脚本会自行完成:

  1. 校验 jar、dist、引擎场景包(WarehouseTwin.swapp)是否齐备
  2. 收集 8 项文件到暂存目录 ..\wms-image-bundle-stage
  3. 打包为 ..\wms-image-bundle.tar.gz(约 105 MB)
  4. 校验包内关键路径是否完整

输出示例:

1
2
3
4
5
6
7
8
9
10
[1/4] 检查构建产物...
后端 jar : wms-server-0.0.1-SNAPSHOT.jar (27.1 MB)
前端 dist: 52 个文件 (105.2 MB)
引擎场景包: 存在
[2/4] 组装暂存目录...
已收集 8 项文件/目录
[3/4] 打包...
E:\project\Java\wms-image-bundle.tar.gz (105.1 MB)
[4/4] 校验包内关键路径...
校验通过,共 72 个条目

构建包与「离线部署包」的区别:构建包里是 jar + dist + 镜像构建配置
用于在另一台机器上 docker compose build;离线部署包里是镜像 tar
用于在目标机上 docker load。二者不要混淆。

A.2 在带 Docker 的机器上构建并导出

1
2
3
4
5
6
scp E:\project\Java\wms-image-bundle.tar.gz root@<该机器IP>:/root/

mkdir -p /root/wms-image-build && cd /root/wms-image-build
tar -xzf /root/wms-image-bundle.tar.gz
docker compose build # 无编译动作,只 COPY,1~2 分钟
docker save -o app-images.tar wms-dt/server:1.0.0 wms-dt/web:1.0.0

随后从第 4.6 节继续(准备基础镜像),再到第 5 章传输、第 6 章部署。