Deploy openGym (gym tracker) with custom manifests #49

Closed
opened 2026-08-21 18:51:58 +00:00 by lab · 1 comment
Owner

Self-host openGym, a self-hosted gym
and body-weight tracker (plan weeks, guided workouts, set logging, weight
charts, passkey login, PWA). AGPL-3.0.

Approach

No upstream Helm chart. Write custom raw manifests following the
speedtest-tracker pattern in k3s-ha-cluster/opengym/:

  • deployment.yaml, service.yaml, pvc.yaml, README.md

Architecture

Single pod, two containers:

  • web: nginx serving the React frontend, proxying /api to the api
    container (port 8080)
  • api: Node.js backend with WebAuthn (passkeys)

Prebuilt images on ghcr.io (amd64 + arm64):
ghcr.io/duartesantos8/opengym-web, ghcr.io/duartesantos8/opengym-api.
Data is plain JSON files in /data -> Longhorn PVC.

Required env for passkeys

- name: RP_ID
  value: opengym.yukselcloud.com
- name: ORIGIN
  value: https://opengym.yukselcloud.com

Passkeys fail silently if RP_ID/ORIGIN do not match the public URL.
First launch downloads ~140MB of exercise media into /data.

Wiring

  • Caddy route opengym.yukselcloud.com in k3s-ha-cluster/caddy/configmap.yaml
  • Cloudflare DNS record (one-click onboarding issue)
Self-host [openGym](https://gitea.com/DuarteSantos/openGym), a self-hosted gym and body-weight tracker (plan weeks, guided workouts, set logging, weight charts, passkey login, PWA). AGPL-3.0. ## Approach No upstream Helm chart. Write custom raw manifests following the speedtest-tracker pattern in `k3s-ha-cluster/opengym/`: - `deployment.yaml`, `service.yaml`, `pvc.yaml`, `README.md` ## Architecture Single pod, two containers: - `web`: nginx serving the React frontend, proxying `/api` to the api container (port `8080`) - `api`: Node.js backend with WebAuthn (passkeys) Prebuilt images on ghcr.io (amd64 + arm64): `ghcr.io/duartesantos8/opengym-web`, `ghcr.io/duartesantos8/opengym-api`. Data is plain JSON files in `/data` -> Longhorn PVC. ## Required env for passkeys ```yaml - name: RP_ID value: opengym.yukselcloud.com - name: ORIGIN value: https://opengym.yukselcloud.com ``` > Passkeys fail silently if `RP_ID`/`ORIGIN` do not match the public URL. > First launch downloads ~140MB of exercise media into `/data`. ## Wiring - Caddy route `opengym.yukselcloud.com` in `k3s-ha-cluster/caddy/configmap.yaml` - Cloudflare DNS record (one-click onboarding issue)
lab closed this issue 2026-08-22 11:57:46 +00:00
Author
Owner

Deployed and exposed. Commit b268c8f.

What shipped

  • Images built from upstream source (podman) and pushed to our own registry:
    git.yukselcloud.com/lab/opengym-api:v1.2.7,
    git.yukselcloud.com/lab/opengym-web:v1.2.7 — no dependency on upstream ghcr
  • Manifests in k3s-ha-cluster/opengym/ (deployment/service/pvc/README):
    one pod, two containers (api :3000 + web nginx :80), media dataset as an
    init container into a shared Longhorn PVC (subPaths data, media/img,
    media/gif)
  • Exposed: Caddy route opengym.yukselcloud.com (log + crowdsec +
    reverse_proxy) and ddns subdomain entry; both applied by Forgejo Actions on
    push

Infrastructure fix found during deploy

New volume mounts failed on node-181 with mke2fs ... apparently in use by the system. Root cause: multipathd.socket had socket-reactivated multipathd,
which claimed freshly attached Longhorn iSCSI devices into dm maps.
multipathd.service + multipathd.socket are now stopped, disabled and
masked
on all four nodes; maps flushed.

Verified

  • Pod Running, init media cached (1324 images), /api/health -> {"ok":true}
  • DNS A record opengym.yukselcloud.com -> 85.156.79.45
  • https://opengym.yukselcloud.com -> 200 through Caddy

Passkey login is now active at the public URL (RP_ID/ORIGIN match).

Deployed and exposed. Commit `b268c8f`. ## What shipped - Images built from upstream source (podman) and pushed to our own registry: `git.yukselcloud.com/lab/opengym-api:v1.2.7`, `git.yukselcloud.com/lab/opengym-web:v1.2.7` — no dependency on upstream ghcr - Manifests in `k3s-ha-cluster/opengym/` (deployment/service/pvc/README): one pod, two containers (`api` :3000 + `web` nginx :80), media dataset as an init container into a shared Longhorn PVC (subPaths `data`, `media/img`, `media/gif`) - Exposed: Caddy route `opengym.yukselcloud.com` (log + crowdsec + reverse_proxy) and ddns subdomain entry; both applied by Forgejo Actions on push ## Infrastructure fix found during deploy New volume mounts failed on node-181 with `mke2fs ... apparently in use by the system`. Root cause: `multipathd.socket` had socket-reactivated multipathd, which claimed freshly attached Longhorn iSCSI devices into dm maps. `multipathd.service` + `multipathd.socket` are now stopped, disabled **and masked** on all four nodes; maps flushed. ## Verified - Pod Running, init media cached (1324 images), `/api/health` -> `{"ok":true}` - DNS A record `opengym.yukselcloud.com -> 85.156.79.45` - `https://opengym.yukselcloud.com` -> 200 through Caddy Passkey login is now active at the public URL (`RP_ID`/`ORIGIN` match).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lab/homelab#49
No description provided.