openGym项目架构总览:2个容器+1个文件夹如何撑起一款现代健身App
【免费下载链接】openGymSelf-hosted gym & body-weight tracker — plan routines, log workouts (supersets, warm-ups, cardio), see which muscles are trained, fatigued or detrained, import from FitNotes/Strong/Hevy, passkey login. Your data, your server.项目地址: https://gitcode.com/GitHub_Trending/op/openGym
openGym 是一款自托管健身与体重追踪 App:计划训练、记录组间与力竭、查看肌肉疲劳与恢复,还能从 FitNotes / Strong / Hevy 导入历史数据,用 Passkey(面容/指纹)登录,数据完全存你自己的服务器。它没有复杂微服务——整个服务端就是2 个容器 + 1 个你拥有的数据文件夹,一条docker compose up就能跑起来。下面用 5 分钟带你看完它的架构全景 🏋️
一、架构 30 秒速览:一张图看懂整体结构
官方架构图中,手机或电脑浏览器通过 HTTPS 只访问web 容器(nginx),由它转发/api请求到api 容器(Node.js);API 把所有数据写成 JSON 文件放进./data文件夹。首次启动时,一个一次性的media 服务会下载约 140 MB 的动作示意图(仅首次),之后不再运行。AI 教练与 MCP 服务器都是可选项,默认关闭:
这套结构对应 docker-compose.yml 里的 3 个 service:
| 服务 | 角色 | 是否常驻 |
|---|---|---|
web | 构建 React 前端 + nginx 托管,代理/api | ✅ 常驻 |
api | 无框架 Node 后端 + Passkey 登录 + 推送通知 | ✅ 常驻 |
media | 首次启动下载动作图片/GIF 到共享卷 | ⚡ 一次性 |
二、web 容器:多阶段构建 + 单端口入口
web/Dockerfile 采用多阶段构建:
- build 阶段:
node:22-alpine里跑 Vite,把frontend/编译成纯静态文件; - serve 阶段:把静态文件拷进
nginx:alpine,启动即用。
自托管者本地完全不需要装 Node。真正的设计亮点在 web/nginx.conf.template:
- 静态页面与
/api代理同处一个 origin(同源),这是WebAuthn Passkey 登录的硬性要求; - 配置模板在容器启动时由环境变量渲染(
NGINX_PORT、BACKEND、PORT、BASE_PATH),意味着改端口、挂子路径/gym部署都不需要重新构建镜像; /api用变量 +resolver实现每次请求动态解析 api 容器地址,避免容器重建后 IP 变化导致 502;- 代理时覆盖(而非追加)
X-Forwarded-For头,防止客户端伪造活动日志里的 IP。
三、api 容器:只有 1 个主文件的 Node 后端
后端 api/server.js 是纯node:http,零框架,依赖少到可以数出来:api/package.json 中生产依赖仅 3 个——@simplewebauthn/server(Passkey 登录)、web-push(休息计时器/训练提醒推送)、undici。加一个可选依赖用于 AI 教练。
几个值得新手学习的设计:
- 状态即 JSON 文件:无数据库。
db.json存用户与凭据,state-<用户id>.json存每个用户的计划、训练记录、体重与设置,写入采用"写临时文件再原子改名"防止损坏; - 路由即对象键:
routes['GET /api/health']这样的键匹配请求方法与路径,加接口只需加一个键; - 会话用 HMAC 签名 Cookie,密钥存在
./data/secret,不依赖 JWT 或服务端会话存储; - 默认与教练双镜像:api/Dockerfile 用两个 build target 从同一份文件出两种镜像——
default不含任何 AI 运行时,coach才装 Claude Agent SDK,并创建一个无权读取/data的降权用户,把 AI 运行时与你的数据隔离开。
四、1 个文件夹:./data就是全部数据
整个项目的"数据库"就是主机上的 ./data 目录,映射进容器的/data:
| 文件 | 内容 |
|---|---|
db.json | 用户档案与 Passkey 公钥 |
state-<用户>.json | 每个用户的计划、训练、体重、设置 |
secret | 会话 Cookie 签名密钥 |
audit.log | 登录/管理活动日志(滚动保留) |
vapid.json | 推送通知密钥(自动生成) |
备份 = 打包这个文件夹,一行tar完事。Passkey 私钥永远在手机的安全硬件或密码管理器里,不上服务器。
五、多设备同步:版本号 + 乐观合并
手机和笔记本同时登录时,同步靠服务端修订号(revision):设备保存时带上它最后看到的版本;如果期间另一台设备写过,服务器返回409 冲突+ 当前文档,设备本地合并后重试。整个过程见官方时序图:
断网期间的修改不会被丢弃,应用会显示离线横幅提示你何时恢复同步。
六、可选扩展:默认关闭的 AI 教练与 MCP 服务器
- AI 教练(
API_TARGET=coach构建):帮你起草一周训练计划、根据记录建议调整,所有改动需你批准,且使用你自己的API Key,跑在你的服务器上,见 docs/AI_COACH.md; - MCP 服务器:只读的标准输入输出桥,让 Claude Desktop、Cursor 等 AI 客户端直接读取你的训练历史与 1RM,不走网络、不占容器,见 mcp/README.md。
七、快速启动步骤
装好 Docker 后:
git clone https://gitcode.com/GitHub_Trending/op/openGym cd openGym cp .env.example .env docker compose up -d打开http://localhost:8080,创建档案即可。首次启动会一次性下载动作媒体(约 140 MB)。想让手机用 Passkey 登录需要 HTTPS 域名,只需改.env两行——docs/SELF_HOSTING.md 覆盖 Cloudflare Tunnel、Caddy、Traefik、nginx 等全部方案,Kubernetes 部署见 docs/SELF_HOSTING_KUBERNETES.md。
八、关键路径速查
| 想了解 | 去哪里看 |
|---|---|
| 项目结构与架构一页通 | CLAUDE.md |
| 训练逻辑(渐进规则、1RM、恢复模型) | frontend/src/lib/progression.js、frontend/src/lib/onerm.js |
| API 全部路由(OpenAPI 规范) | api/openapi.yaml |
| 常见问题(iPhone、费用、数据去向) | docs/FAQ.md |
| 手机端 APK 与 PWA 方案 | docs/MOBILE.md |
结语:极简,却五脏俱全
openGym 的架构哲学可以概括为一句话:把"现代 App 的体验"装进"两个容器 + 一个文件夹"里。单 origin 保 Passkey、JSON 文件保可备份、模板化 nginx 保零构建部署、原子写入保数据安全——每个设计决策都服务于"你的数据,你的服务器"这一承诺。对于想学习小型自托管服务如何从零搭起的同学,这个仓库几乎是最好的范本 📦
【免费下载链接】openGymSelf-hosted gym & body-weight tracker — plan routines, log workouts (supersets, warm-ups, cardio), see which muscles are trained, fatigued or detrained, import from FitNotes/Strong/Hevy, passkey login. Your data, your server.项目地址: https://gitcode.com/GitHub_Trending/op/openGym
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考