worldmonitor v2.11.4 Docker Compose 部署指南

从源码构建并部署 worldmonitor v2.11.4 官方自托管全栈:四个必需密钥、镜像构建、种子数据灌入、MCP 密钥与 CLI 接线全流程,含数据持久化、运维命令与七个实测排障问题。

worldmonitorworldmonitor自托管仪表盘2026/10/25

worldmonitor v2.11.4 Docker Compose 部署指南

目标:用 Docker Compose 在一台 Linux 服务器上从源码构建并部署 worldmonitor v2.11.4 官方自托管全栈(nginx+Node API + Redis + Redis REST 代理 + AIS 中继),完成必需密钥生成、种子数据灌入、MCP 密钥配置与 CLI 接线,实现「浏览器打开仪表盘 + worldmonitor 命令行直连自家栈」的完整可用状态。本文全部命令与回显已在 Docker Engine 29.9.2(Compose v5.6.1)、Alpine 3.24.3 服务器环境实测通过,部署版本为 v2.11.4 快照(main 分支 commit d8ba38d,2026-10-24)。

一、环境信息

  • 操作系统:Alpine Linux 3.24.3(x86_64,自带 git 2.54.0 / OpenSSL 3.5.9,另安装 Node.js v24.22.1 + npm 11.12.1 用于宿主侧种子脚本)
  • Docker Engine:29.9.2,Docker Compose 插件 v5.6.1
  • worldmonitor 版本:v2.11.4(2026-10-24 发布;无官方 registry 镜像,本篇从源码构建,见第二章)
  • 镜像清单(构建产物实测体积):worldmonitor:latest 787MB、worldmonitor-ais-relay:latest 562MB、worldmonitor-redis-rest:latest 255MB、docker.io/redis:7-alpine 57.8MB
  • 端口规划:仪表盘宿主端口 8140(服务内端口 8080,经 .env 的 WM_PORT 指定);Redis REST 代理 127.0.0.1:8079(仅回环);AIS 中继 3004 与 Redis 6379 仅栈内互通不对外发布。生产环境建议 WM_PORT=3000 直接对外或置于反代之后,本文取值仅为与实测环境保持一致,可按需替换
  • 编排目录:/opt/worldmonitor/(git clone 产物,含 docker-compose.yml、Dockerfile、SELF_HOSTING.md、.env)
  • 编排项目名:worldmonitor-2-11-4-stack(经 docker compose -p 显式指定,栈内自动生成同名 _default bridge 网络与 worldmonitor-2-11-4-stack_redis-data 数据卷)

二、镜像选型说明

worldmonitor 不发布官方 registry 镜像,官方自托管路径就是「源码构建」(docker-compose.yml 的三个服务均带 build: 段,README Quick Start 与 SELF_HOSTING.md 均以 docker compose up -d --build 为标准动作)。选型要点集中在构建产物与基础镜像两点:

项目取值说明
主服务镜像worldmonitor:latest(源码 Dockerfile 构建)多阶段构建:builder 装依赖并产出 Vite 前端 + 编译 TS API 处理器 → runtime-deps 只装运行时依赖 → final 由 nginx(静态+反代)+ Node API(supervisord 托管)组成,以非 root 用户 appuser 运行,内置健康检查探针 /api/sidecar-health
AIS 中继镜像worldmonitor-ais-relay:latest(源码 Dockerfile.relay 构建)WebSocket → AISStream 实时船舶流中继
REST 代理镜像worldmonitor-redis-rest:latest(源码 docker/Dockerfile.redis-rest 构建)Upstash 兼容 REST 代理,只放行固定 Redis 命令白名单
Redis 基础镜像docker.io/redis:7-alpine(官方上游)以 --requirepass + --maxmemory 256mb --maxmemory-policy allkeys-lru 运行

💡 说明:基础镜像 node:24-alpine 在 Dockerfile 中按 sha256 摘要锁定(node:24-alpine@sha256:feb766…),保证构建可复现;主镜像内 nginx、supervisord、gettext(envsubst 用)来自 Alpine 包管理器。镜像标签统一为 :latest——如需版本化,可在 docker compose build 后自行 docker tag 标注构建日期。

⚠️ 网络说明:构建过程需要访问 npm registry 与 Docker Hub。本实测环境直连境外源不稳定(同一环境跑官方 CLI 曾实测报 Error: fetch failed),故构建时给 BuildKit 传入预定义代理参数 --build-arg HTTP_PROXY=… --build-arg HTTPS_PROXY=… 走代理完成,docker pull redis:7-alpine 由守护进程代理拉取。可直连官方源的服务器无需传任何代理参数;若 npm ci 仍超时,可改走国内 npm 镜像源后重试。

三、前置条件

  1. 服务器已安装 Docker Engine 与 Compose 插件,验证:
docker version --format 'Engine {{.Server.Version}}'

实测输出:

Engine 29.9.2
docker compose version

实测输出:

Docker Compose version v5.6.1
  1. 宿主侧安装 Node.js 22+ 与 OpenSSL(种子脚本在宿主机运行,不在栈内跑):
apk add --no-cache git nodejs npm openssl bash 2>&1 | tail -3

实测输出(Alpine 服务器;Debian/Ubuntu 用 apt install -y git nodejs npm openssl bash,Node 需 ≥22):

OK: 101.6 MiB in 65 packages
node -v && openssl version

实测输出:

v24.22.1
OpenSSL 3.5.9 29 Sep 2026 (Library: OpenSSL 3.5.9 29 Sep 2026)
  1. 克隆源码(浅克隆即可):
cd /opt && git clone --depth 1 https://github.com/koala73/worldmonitor.git && cd worldmonitor && git log -1 --format='%h %cd' --date=short

实测输出:

Cloning into 'worldmonitor'...
d8ba38d8246fac3035b3eacc68d1e4dd9193a69f 2026-10-24
du -sh /opt/worldmonitor

实测输出:

253.3M	/opt/worldmonitor
  1. 端口确认:规划仪表盘端口(本篇取 8140)未被占用:
ss -ltn | grep -c ':8140 ' || echo '端口 8140 空闲'

实测输出:

0
端口 8140 空闲

四、部署步骤

4.1 生成四个必需密钥

自托管栈的四个密钥没有安全默认值——缺任何一个,对应服务直接拒绝启动(.env.example 与 SELF_HOSTING.md 均强调;wm-local-token 旧默认值已因 #3804 安全问题移除)。一次性写入 .env:

cd /opt/worldmonitor
printf "RELAY_SHARED_SECRET=%s\nREDIS_PASSWORD=%s\nREDIS_TOKEN=%s\nWM_SESSION_SECRET=%s\n" \
  "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" \
  "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > .env
printf "WM_PORT=8140\n" >> .env
wc -l .env

实测输出:

5 .env

四个密钥的用途(来源:SELF_HOSTING.md「Required Environment Variables」表,实测与之一致):

变量用途缺失后果(实测)
RELAY_SHARED_SECRET认证仪表盘发给 AIS 中继的一切非公开请求中继拒绝启动
REDIS_PASSWORDRedis AUTH 密码(--requirepass)Redis 拒绝启动
REDIS_TOKENREST 代理(redis-rest)每请求 Bearer 令牌,应用侧即 UPSTASH_REDIS_REST_TOKEN代理与应用拒绝启动
WM_SESSION_SECRET给自托管 API 路由的匿名浏览器会话签名应用拒绝启动
WM_PORT仪表盘对外端口(compose 里 ${WM_PORT:-3000}:8080)不设则用默认 3000

缺密钥时的报错形态(实测,用于排障对照):

REDIS_TOKEN= docker compose -p worldmonitor-2-11-4-stack config

实测输出:

error while interpolating services.redis-rest.environment.SRH_TOKEN: required variable REDIS_TOKEN is missing a value: REDIS_TOKEN required — generate with: openssl rand -hex 32
error while interpolating services.worldmonitor.environment.UPSTASH_REDIS_REST_TOKEN: required variable REDIS_TOKEN is missing a value: REDIS_TOKEN required — generate with: openssl rand -hex 32

4.2 构建镜像

首次构建包含 npm ci 依赖安装、TS API 处理器编译、/pro 页面构建与 Vite 前端构建,本实测约 15 分钟:

docker compose -p worldmonitor-2-11-4-stack build \
  --build-arg HTTP_PROXY=http://192.168.1.62:7897 \
  --build-arg HTTPS_PROXY=http://192.168.1.62:7897 \
  --build-arg NO_PROXY=localhost,127.0.0.1

实测输出(输出节选,实测首尾;代理参数按自身网络替换,可直连则省略):

#38 [runtime-deps 3/3] RUN npm ci --omit=dev --omit=optional --ignore-scripts
#38 DONE 51.9s
...
#56 [final 6/8] COPY docker/nginx.conf /etc/nginx/nginx.conf.template
#56 DONE 0.2s
...
 => => naming to docker.io/library/worldmonitor:latest

构建完成后确认三个自建镜像与一个上游镜像:

docker images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}'

实测输出:

worldmonitor:latest            787MB
worldmonitor-ais-relay:latest  562MB
worldmonitor-redis-rest:latest 255MB
redis:7-alpine                 57.8MB

4.3 启动全栈

docker compose -p worldmonitor-2-11-4-stack up -d

实测输出:

 Container worldmonitor-redis Starting
 Container worldmonitor-redis Started
 Container worldmonitor-redis-rest Starting
 Container worldmonitor-redis-rest Started
 Container worldmonitor-ais-relay Starting
 Container worldmonitor-ais-relay Started
 Container worldmonitor Starting
 Container worldmonitor Started
docker compose -p worldmonitor-2-11-4-stack ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}'

实测输出(启动约半分钟后健康检查转 healthy):

NAME                       STATUS                   PORTS
worldmonitor               Up 8 seconds (healthy)   0.0.0.0:8140->8080/tcp, [::]:8140->8080/tcp
worldmonitor-ais-relay     Up 4 minutes (healthy)   3004/tcp
worldmonitor-redis         Up 4 minutes             6379/tcp
worldmonitor-redis-rest    Up 4 minutes             127.0.0.1:8079->80/tcp

主服务启动日志(docker compose -p worldmonitor-2-11-4-stack logs worldmonitor,输出节选,实测首部)——可见 supervisord 拉起 nginx 与 Node API、API 共 184 条路由、健康检查探针已通:

worldmonitor  | 2026-10-25 02:03:20,692 INFO supervisord started with pid 1
worldmonitor  | 2026-10-25 02:03:21,693 INFO spawned: 'nginx' with pid 30
worldmonitor  | 2026-10-25 02:03:21,698 INFO spawned: 'worldmonitor-api' with pid 31
worldmonitor  | [local-api] listening on http://127.0.0.1:46123 (apiDir=/app/api, routes=184, cloudFallback=false)
worldmonitor  | 2026-10-25 02:03:22,778 INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
worldmonitor  | 127.0.0.1 - [25/Oct/2026:02:03:25 +0000] "GET /api/sidecar-health HTTP/1.1" 200 55

4.4 验证仪表盘与健康端点

curl -s -o /dev/null -w 'GET http://localhost:8140/ -> HTTP %{http_code}\n' http://localhost:8140/
curl -s http://localhost:8140/ | grep -oE '<title>[^<]*</title>' | head -1
curl -s http://localhost:8140/api/sidecar-health

实测输出:

GET http://localhost:8140/ -> HTTP 200
<title>World Monitor - Real-Time Global Intelligence Dashboard</title>
{"status":"ok","mode":"docker","port":46123}

再确认前端资源、/pro 页面与 dashboard 路由(SPA 回退目标)均可达:

curl -s -o /dev/null -w 'main.js HTTP %{http_code} bytes=%{size_download}\n' http://localhost:8140/assets/main-CDZonSH6.js
curl -s -o /dev/null -w '/pro/ HTTP %{http_code}\n' http://localhost:8140/pro/
curl -s -o /dev/null -w '/dashboard.html HTTP %{http_code}\n' http://localhost:8140/dashboard.html

实测输出:

main.js HTTP 200 bytes=134478
/pro/ HTTP 200
/dashboard.html HTTP 200

此时打开浏览器访问 http://<服务器IP>:8140/ 即见完整仪表盘(世界地图、新闻、市场等面板)。数据健康总览端点在灌数前会显示大量 EMPTY(详见 4.5):

curl -s 'http://localhost:8140/api/health?compact=1' | head -c 220

实测输出(灌数前;crit:210 即种子数据未灌入):

{"status":"UNHEALTHY","summary":{"total":335,"ok":23,"warn":33,"containedWarn":1,"onDemandWarn":69,"staleContent":0,"rolloutPending":0,"crit":210},"checkedAt":"2026-10-25T02:01:46.112Z","problems":{"earthquakes":{"status":"EMPTY","records":0,"maxStaleMin":30},"outages":{"status":"EMPTY","records":0,"maxStaleMin":30},"sectors":{"status":"EMPTY","records":0,"maxStaleMin":30},"etfFlows":{"status":"EMPTY","records":0,"maxStaleMin":60},"climateAnomalies":{"status":"EMPTY","records":0,"maxStaleMin":540},"climateDisasters":{"status":"EMPTY","records":0,"maxStaleMin":720},"climateAirQuality":{"status"

4.5 灌入种子数据

仪表盘的缓存型面板(地震、天气、经济指标等)由种子脚本从上游抓数写入 Redis。脚本在宿主机运行(不在栈内),需 Redis REST 代理已启动;run-seeders.sh 会自动从 .env 读 REDIS_TOKEN:

cd /opt/worldmonitor && ./scripts/run-seeders.sh

实测输出(输出节选,实测首部;仓库共 226 个 scripts/seed-*.mjs,独立脚本逐个输出 OK/FAIL,bundle 脚本输出 SKIP 并附内部统计——FAIL/SKIP 均为优雅降级,不中断整体流程):

→ seed-aaii-sentiment.mjs ... FAIL (=== Failed gracefully (1458ms) ===)
→ seed-alberta-emergency-alert.mjs ... OK
→ seed-aviation.mjs ... FAIL (Node.js v24.22.1)
→ seed-bc-emergency-info.mjs ... OK
→ seed-bigmac.mjs ... FAIL (=== Done (5002ms, RETRY FAILED) ===)
→ seed-bis-data.mjs ... OK
→ seed-bis-extended.mjs ... OK
→ seed-bis-lbs.mjs ... OK
→ seed-boc-valet.mjs ... OK
→ seed-bundle-canada.mjs ...

跑一段时间后复查健康总览(本实测跑至约 20 个脚本后,ok 从 23 升至 79、crit 从 210 降至 169):

curl -s 'http://localhost:8140/api/health?compact=1' | head -c 200

实测输出(节选,实测首部):

{"status":"UNHEALTHY","summary":{"total":335,"ok":79,"warn":32,"containedWarn":1,"onDemandWarn":55,"staleContent":0,"rolloutPending":0,"crit":169},"checkedAt":"2026-10-25T02:09:15.262Z",

💡 说明:全量 226 个脚本按默认每脚本 30 分钟超时上限跑完需要较长时间,生产建议交给 cron 定时补数(SELF_HOSTING.md 给出 */30 * * * * 示例);部分脚本无对应 API key 或上游被墙时永远 FAIL,属预期行为,不影响其余面板。每个独立脚本受 SEED_TIMEOUT 秒级墙钟上限保护(默认 1800,SEED_TIMEOUT=0 关闭),防止单个挂起的上游拖死整轮灌数。

4.6 配置 MCP 密钥并接通 CLI

自托管栈自带 MCP 服务器(/api/mcp),用 X-WorldMonitor-Key 头认证,合法 key 由 .env 的 WORLDMONITOR_VALID_KEYS 定义(OAuth 仅官方托管版提供)。生成 key 并重建应用:

cd /opt/worldmonitor
KEY="wm_$(openssl rand -hex 20)"
echo "WORLDMONITOR_VALID_KEYS=$KEY" >> .env
docker compose -p worldmonitor-2-11-4-stack up -d

实测输出:

 Container worldmonitor Recreated
 Container worldmonitor Starting
 Container worldmonitor Started

裸协议验证 MCP(tools/list):

curl -s -X POST http://localhost:8140/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H "X-WorldMonitor-Key: $KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | head -c 220

实测输出(节选,实测首部):

{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_country_history","title":"Country daily history","description":"Read cached daily country CII, resilience and linked headlines. Account depth: Pro 90 days; API/Enterprise 365. ...

再用官方 CLI 直连自家栈(CLI 安装见 《worldmonitor v2.11.4 常用 CLI 命令与工具详解》):

worldmonitor call get_sources --mcp-url http://localhost:8140/api/mcp --api-key "$KEY" 2>&1 | head -10

实测输出(节选,实测首部):

{
  "content": [
    {
      "type": "text",
      "text": "{\"view\":\"summary\",\"summary\":{\"providerCount\":790,\"excludedProviderCount\":171,\"outletCount\":531,\"providersByKind\":{\"feed\":406,\"structured\":291,\"operational-status\":30,\"feed+structured\":63},...

到此部署闭环完成:浏览器访问 http://<服务器IP>:8140/ 看仪表盘,CLI/MCP 客户端经 http://<服务器IP>:8140/api/mcp 编程取数。

五、数据持久化说明

自托管栈只有一仞需要持久化的状态——Redis 种子数据,落在 compose 自动创建的命名卷中:

docker volume ls --format '{{.Name}}' | grep worldmonitor

实测输出:

worldmonitor-2-11-4-stack_redis-data
  • 重启不丢:docker compose restart / 服务器重启(restart: unless-stopped 自动拉起)后 Redis 数据仍在,面板无需重灌;
  • down 不带 -v 不丢:docker compose down 保留卷;down -v 会删卷,所有种子数据清零,需重新执行 ./scripts/run-seeders.sh(SELF_HOSTING.md 原文警告);
  • 种子数据有时效:种子内容按 TTL 过期,靠 cron 定时补数保持新鲜;看到面板 stale/EMPTY 先跑一轮 seeders 再排查;
  • 用户偏好(面板顺序、监视关键词等)存在浏览器 LocalStorage/IndexedDB,不在服务端,换浏览器需重新配置。

六、环境变量表

docker-compose.yml 从 .env 插值。必填 4 项缺失即拒绝启动;其余可选项按功能开启(值留空 = 对应功能关闭,核心功能不受影响)。

6.1 必填项(本篇实测全部设置)

变量示例来源用途
RELAY_SHARED_SECRETopenssl rand -hex 32AIS 中继鉴权共享密钥
REDIS_PASSWORDopenssl rand -hex 32Redis AUTH 密码(--requirepass,并用于 redis-rest 上游连接串)
REDIS_TOKENopenssl rand -hex 32redis-rest 的 Bearer 令牌(SRH_TOKEN)与应用侧 UPSTASH_REDIS_REST_TOKEN
WM_SESSION_SECRETopenssl rand -hex 32匿名浏览器会话签名密钥

6.2 高频可选项(值留空功能即隐藏)

变量用途获取途径
WM_PORT仪表盘对外端口(默认 3000)自定
WORLDMONITOR_VALID_KEYS自托管 MCP/REST 的 operator key(wm_ 前缀)wm_$(openssl rand -hex 20)
OPENROUTER_API_KEY云端 LLM(免费档 50 次/天)openrouter.ai
LLM_API_URL / LLM_API_KEY / LLM_MODEL自托管 LLM(任意 OpenAI 兼容端点,如 Ollama http://<内网主机>:11434/v1/chat/completions)自建
FINNHUB_API_KEY股票行情主源(配额缺口 + 符号搜索)finnhub.io(免费档)
AISSTREAM_API_KEY实时船舶 AIS(app 与 ais-relay 两个服务都要给)aisstream.io(免费)
EIA_API_KEY石油分析eia.gov/opendata(免费)
FRED_API_KEY美联储经济数据fred.stlouisfed.org(免费)
ACLED_EMAIL / ACLED_PASSWORD冲突与抗议数据(OAuth 自动刷新,优于静态 token)acleddata.com(研究者免费)
NASA_FIRMS_API_KEY卫星火点(seed-fire-detections 脚本必需)firms.modaps.eosdis.nasa.gov(免费)
CLOUDFLARE_API_TOKEN断网监测(需 Radar 访问权限,付费)dash.cloudflare.com
OPENSKY_CLIENT_ID / OPENSKY_CLIENT_SECRET军航追踪(经中继)opensky-network.org(免费)
WM_TRUSTED_PROXY_CIDRS前置反代时声明可信代理网段(逗号分隔,非法值拒绝启动;直连勿设)自定

完整可配置项超过 250 个(api/、scripts/、前端 VITE_ 特性开关等),全集见仓库 .env.example(1297 行)与本专栏配置详解篇。

七、配置参数说明

docker-compose.yml 内写死的关键运行参数(自托管一般不动,理解含义即可):

参数值说明
LOCAL_API_MODEdocker启用 Docker 自托管模式:无 Clerk/Convex 权益后端,/api/local- 管理路由一律 403,运维改动走 Compose 环境变量重建
LOCAL_API_PORT46123栈内 Node API 监听端口(nginx 把 /api/* 反代过去,不对外发布)
LOCAL_API_CLOUD_FALLBACKfalse关闭云端回退,纯本地模式
WS_RELAY_URLhttp://ais-relay:3004应用侧连 AIS 中继的栈内地址
UPSTASH_REDIS_REST_URLhttp://redis-rest:80应用/中继经 REST 代理访问 Redis
UPSTASH_ALLOW_INSECURE_HTTPtrue(仅 ais-relay)允许中继容器走纯 HTTP 代理(默认要求 https,自托管代理是 http)
--maxmemory 256mb / allkeys-lruRedis 启动参数缓存上限 256MB,满了按 LRU 淘汰
mem_limit: 512mredis-restREST 代理会整体缓冲请求体(默认上限 16MB),限 512MB 内存防并发大请求放大

💡 说明:Docker 模式下仅 GET /api/intelligence/v1/get-country-intel-brief 接受匿名会话,且每日每 IP 限 50 次直连 LLM 调用(nginx 以 $remote_addr 打 X-Real-IP,轮换头或会话都无法重置配额);其余 premium 路由仍需 API key。若在栈前另加反代,必须设 WM_TRUSTED_PROXY_CIDRS 指向该反代,否则限流可被伪造转发头绕过或误伤。

八、配置文件挂载与密钥注入

本栈不挂载配置文件,配置全部经环境变量注入;推荐用 gitignore 的 docker-compose.override.yml 注入 API key(不污染主文件、不进版本库)。创建 /opt/worldmonitor/docker-compose.override.yml:

services:
  worldmonitor:
    environment:
      OPENROUTER_API_KEY: "${OPENROUTER_API_KEY}"
      FINNHUB_API_KEY: "${FINNHUB_API_KEY}"
      LLM_API_URL: "http://192.168.1.100:11434/v1/chat/completions"
      LLM_MODEL: "qwen3:8b"
  ais-relay:
    environment:
      AISSTREAM_API_KEY: "${AISSTREAM_API_KEY}"

同目录 .env 追加对应值后重建生效(override 里的 ${VAR} 从 .env 插值,密钥不落命令行历史):

echo "FINNHUB_API_KEY=${FINNHUB_API_KEY}" >> .env   # 实际值由运维在 .env 中填入
docker compose -p worldmonitor-2-11-4-stack up -d

💡 说明:除 override 文件外,compose 还预留了 Docker secrets 注入形态(主文件注释段:secrets/ 目录每 key 一个文件,如 echo "sk-or-abc123" > secrets/openrouter_api_key.txt),适合不想让 key 出现在 docker inspect 输出的场景,两种方式二选一即可。自托管 LLM 若主机名不可解析,在 override 里加 extra_hosts: ["your-host:192.168.1.100"](SELF_HOSTING.md 原文示例)。

九、等价 docker run

理解 compose 拓扑后,可用单条 docker run 把构建好的 worldmonitor:latest 跑成一个独立验证实例(挂到栈内网络、指向 redis-rest,对外 8141),验证后即删:

cd /opt/worldmonitor && set -a && . ./.env && set +a
docker run -d --name worldmonitor-run-eq -p 8141:8080 \
  -e UPSTASH_REDIS_REST_URL=http://redis-rest:80 \
  -e UPSTASH_REDIS_REST_TOKEN=$REDIS_TOKEN \
  -e WM_SESSION_SECRET=$WM_SESSION_SECRET \
  -e LOCAL_API_PORT=46123 \
  -e LOCAL_API_MODE=docker \
  -e LOCAL_API_CLOUD_FALLBACK=false \
  -e WS_RELAY_URL=http://ais-relay:3004 \
  -e RELAY_SHARED_SECRET=$RELAY_SHARED_SECRET \
  --network worldmonitor-2-11-4-stack_default worldmonitor:latest
sleep 10 && curl -s http://localhost:8141/api/sidecar-health
docker rm -f worldmonitor-run-eq

实测输出:

{"status":"ok","mode":"docker","port":46123}

💡 说明:正式运行请回到 compose 管理(restart: unless-stopped、健康检查、与 redis/中继的编排关系都由 compose 提供),docker run 仅用于快速验证镜像与密钥是否正常。

十、常用运维命令

以下命令均在 /opt/worldmonitor/ 目录内执行(-p worldmonitor-2-11-4-stack 与部署时保持一致):

docker compose -p worldmonitor-2-11-4-stack ps                      # 四服务状态与健康检查
docker compose -p worldmonitor-2-11-4-stack logs -f worldmonitor    # 跟踪主服务日志(Ctrl+C 退出)
docker compose -p worldmonitor-2-11-4-stack restart worldmonitor    # 只重启主服务
docker compose -p worldmonitor-2-11-4-stack down                    # 停栈(保留数据卷)
docker compose -p worldmonitor-2-11-4-stack up -d                   # 依 .env/override 重建启动

重启主服务实测输出:

 Container worldmonitor Restarting
 Container worldmonitor Started
curl -s -o /dev/null -w '重启后 GET / -> HTTP %{http_code}\n' http://localhost:8140/

实测输出:

重启后 GET / -> HTTP 200

升级到新版本(官方发布极快,四天三个补丁版;升级 = 换源码 → 重建镜像 → 重灌种子):

cd /opt/worldmonitor
git pull --ff-only                                   # 或 git fetch --tags && git checkout v2.11.5
docker compose -p worldmonitor-2-11-4-stack build    # 依赖不变时命中层缓存,只重跑变更层
docker compose -p worldmonitor-2-11-4-stack up -d
./scripts/run-seeders.sh                             # 新版本新增面板需要补数

回滚:git checkout v2.11.3 切回上一版源码后重复 build + up -d 即可;Redis 数据卷与新版本向后兼容,无需删除。

密钥轮换:重写 .env 中对应项后 docker compose up -d 重建(必填密钥无默认值,全部可随时轮换;WORLDMONITOR_VALID_KEYS 轮换后客户端需同步换 key)。

十一、常见问题(FAQ)

Q1:up -d 直接报 required variable ... is missing a value? 四个必填密钥(RELAY_SHARED_SECRET/REDIS_PASSWORD/REDIS_TOKEN/WM_SESSION_SECRET)至少缺一。按 4.1 用 openssl rand -hex 32 补齐到 .env 再 up -d。实测报错形态见 4.1 节。老克隆升级到新版本尤其要补——旧版默认 wm-local-token 已被移除(#3804)。

Q2:构建阶段 npm ci 报错或卡死? 先确认代理/直连渠道(见第二章 ⚠️ 网络说明)。SELF_HOSTING.md 给出的官方解法:锁文件与构建机 npm 版本不匹配时,用容器重建锁文件后再构建:

docker run --rm -v "$(pwd)":/app -w /app node:24-alpine npm install --package-lock-only

Q3:仪表盘打开但大量面板空白/显示 stale? 种子数据未灌或已过期。先跑 ./scripts/run-seeders.sh,再用 curl 'http://localhost:8140/api/health?compact=1' 复查 ok/crit 计数(本实测灌数前 ok:23/crit:210,灌数中 ok:79/crit:169,随 seeders 推进持续改善)。上游需要 API key 的面板(船舶、军航、断网等)无 key 时永远为空,属预期。

Q4:seeders 大量 FAIL / SKIP? 独立脚本 FAIL、bundle 脚本 SKIP 都是设计内的优雅降级:无对应 API key、上游临时不可达时该脚本跳过,其余继续。按需在 docker-compose.override.yml/.env 补 key 后重跑即可。单脚本挂死由 SEED_TIMEOUT(默认 1800 秒)强制截断。

Q5:管理接口全部 403? Docker 模式(LOCAL_API_MODE=docker)下 /api/local- 管理路由一律 403,内部 sidecar 令牌不授予管理员权限——这是 SELF_HOSTING.md 明文设计。运维改动一律走 Compose 环境变量/override 重建,或使用桌面端原生管理功能。

Q6:服务器带出网代理时,官方 CLI 报 Error: fetch failed? Node 24 的 fetch 默认不读代理环境变量。实测在命令前加 NODE_USE_ENV_PROXY=1(Node 24 内置开关)即可让 CLI 走 HTTP_PROXY/HTTPS_PROXY 访问官方端点;连自家栈(--mcp-url http://localhost:8140/api/mcp)则无需代理。

Q7:redis-rest 报 413 Payload Too Large? 代理默认接受 16MB 请求体(SRH_MAX_BODY_BYTES),种子负载上限 5MB/键。调大上限时需同步上调 redis-rest 的 mem_limit(代理整体缓冲请求体,实测注释:64 个并发 16MB POST 可达约 933MB RSS)。

十二、参考链接