Files
onlyinone/server/docker-compose.yml
jkooe 1e9a5ff72e feat(deploy): 服务端 Docker 化 + 本机部署文档 + 修正客户端默认地址
新增(server/):
- Dockerfile:node:22-alpine、非 root(node uid 1000)、npm ci --omit=dev、
  HEALTHCHECK 用 /api/v1/health(alpine 无 curl,改用 node fetch)、exec 形式 CMD。
  node:sqlite 是 Node 内置模块,alpine/musl 上实测可用,无需换 slim。
- .dockerignore:排除 .env / data / test / smoke.mjs / coverage / docs,
  保证密钥与用户密码哈希不进镜像层。
- docker-compose.yml:3100:3000(避开本机已被占用的 3000)、env_file 读 .env、
  bind mount ./data:/app/data(用户要能直接看到/拷走 onlyinone.db)、
  restart: unless-stopped、init: true、healthcheck。

docs/deploy.md:TL;DR、首次部署、客户端填什么、局域网、升级/备份、故障表、设计取舍。

修正 DEFAULT_BASE_URL:
`https://api.onlyinone.app` 是从不存在的域名,用户不填地址就永远连接失败。
改为 `http://127.0.0.1:3100`(与 compose 宿主机端口一致),本机 Docker 开箱即用。
选「默认可用」而非「留空+强制 UI 提示」:改动面只有一行常量、不碰 UI 逻辑,
而本机试跑正是首要场景;局域网需手填 IP,已在文档写明。

测试:新增默认地址回归防护(不可为空、必须能过 normalizeBaseUrl 且不被改写、
必须是纯 origin、端口须为 3100)。launcher 525/525、server 82/82。
2026-10-27 01:01:26 +08:00

82 lines
3.6 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Only in One - 账号服务端(本机 Docker 部署)
#
# 三条命令跑起来(详见 docs/deploy.md):
# cp .env.example .env # 然后把 JWT_SECRET 填进去(必填,否则容器拒绝启动)
# docker compose up -d --build
# curl http://127.0.0.1:3100/api/v1/health
#
# 刻意**不写** `version:` 字段 —— compose v2 已废弃它,写了只会刷一条 WARNING。
services:
# 服务名刻意与 container_name 保持一致。
# 原因:`docker compose exec / logs / restart` 后面跟的是**服务名**而不是容器名,
# 两者不同名时 `docker compose exec onlyinone-server ...` 会报
# 「service "onlyinone-server" is not running」,非常容易踩。
onlyinone-server:
build:
context: .
dockerfile: Dockerfile
image: onlyinone-server:local
container_name: onlyinone-server
restart: unless-stopped
# 宿主机 3100 → 容器 3000。
# 为什么不写 3000:3000:用户机器上 3000 已被另一个容器占用
# (`lsof -nP -iTCP:3000 -sTCP:LISTEN` 显示 com.docke 在监听),映射过去会直接
# EADDRINUSE 起不来。容器内端口恒为 3000,只改左边这一位。
ports:
- "3100:3000"
# 密钥从宿主机 .env 读,不进镜像层。
env_file:
- .env
# 下面几项显式声明 → 优先级高于 env_file(compose 的 environment 覆盖 env_file)。
# 目的:防止用户在 .env 里把 PORT 改掉,导致「端口映射 3100:3000」与
# 「真实监听 3101」错位,表现为 healthcheck 永远失败。
# 要换对外端口请改上面 ports 的左边,不要动这里。
environment:
NODE_ENV: production
HOST: 0.0.0.0
PORT: "3000"
DB_PATH: /app/data/onlyinone.db
# 数据持久化选 **bind mount** 而不是 named volume。
# 理由:用户的核心诉求是「能直接看到 / 备份数据库」。bind mount 之后
# onlyinone.db 就是宿主机上的 server/data/onlyinone.db,Finder 里双击就能看到、
# 拷走就是备份;named volume 得先 `docker volume inspect` 找到
# /var/lib/docker/volumes/... 的真实路径,在 macOS 上还得进虚拟机,用户碰不到。
# 代价:Linux 上宿主机目录的 uid 需要与容器内 node(1000) 匹配,见 deploy.md 排障表。
volumes:
- ./data:/app/data
# 用 tini 当 PID 1。
# 不装的话 node 本身就是 PID 1,而 **Linux 内核不会把「默认动作是终止」的
# SIGTERM 投递给 PID 1**(除非进程自己装了 handler)。server.js 没装 SIGTERM
# handler,于是 `docker stop` 会静默等满 10s 超时再 SIGKILL。
# 加上 init 后 node 变成子进程,SIGTERM 正常转发,停容器是秒级。
init: true
# healthcheck 在 compose 层再声明一次(与 Dockerfile 里的一致):
# 好处是 `docker ps` / `docker compose ps` 能直接看到 healthy 状态列。
healthcheck:
test:
- CMD
- node
- -e
- "fetch('http://127.0.0.1:3000/api/v1/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
# 日志:server/logger.js 是**双写** —— 同时写 stdout 和 ${logDir}/server.log。
# stdout 那份由 docker 收集(就是 json-file),下面做大小轮转防止无限增长;
# 文件那份落在 bind mount 里 → server/data/logs/server.log(自带 5MB×3 轮转)。
# 两种看法见 deploy.md。
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"