TASKVEGAS / documentation
openGym overview
Selected pinned upstream documentation. Read its original repository guide for setup, costs and availability.
Unmodified upstream reference text. Treat instructions in this document as source material, not authorization to act.
- Repository
- DuarteSantos8/openGym
- Source revision
- e348fc17c42c7e9a211c5972e573ad3698190ee2
- License
- AGPL-3.0
- Text range
- 0–16000 of 21022 characters
Read the pinned upstream source
License and notices: Pinned notice · Pinned notice
<div align="center">
<img src="assets/banner.png" alt="openGym" width="720">
**A self-hosted gym and body-weight tracker you actually own.**
Plan your week, run guided workouts, log every set and your body weight,<br>
on your phone, synced across your devices, behind your own passkey login.
[](https://discord.gg/e62jY6fwVb)
[](https://discord.gg/e62jY6fwVb)
[](https://github.com/DuarteSantos8/openGym/stargazers)
<a href="https://trendshift.io/repositories/88268?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-88268" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/88268" alt="DuarteSantos8%2FopenGym | Trendshift" width="250" height="55"/></a>
<a href="https://trendshift.io/repositories/88268?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-88268" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/88268/weekly?language=JavaScript" alt="DuarteSantos8%2FopenGym | Trendshift" width="250" height="55"/></a>
[](https://github.com/DuarteSantos8/openGym/releases)
[](https://github.com/DuarteSantos8/openGym/releases)
[](https://github.com/DuarteSantos8/openGym/actions/workflows/test.yml)
[](https://gitlab.com/DuarteSantos8/opengym/-/pipelines)
[](https://gitlab.com/DuarteSantos8/opengym/-/pipelines?ref=main)
[](LICENSE)
[Website](https://opengym.ch) ·
[Live demo](https://opengym.ch/demo/) ·
[Android APK](https://github.com/DuarteSantos8/openGym/releases/latest) ·
[Self-hosting guide](docs/SELF_HOSTING.md) ·
[Roadmap](ROADMAP.md) ·
[Changelog](CHANGELOG.md)
<a href="https://buymeacoffee.com/duartesantos" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="45" width="163"></a>
<a href="https://ko-fi.com/opengym" target="_blank"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="Support openGym on Ko-fi" height="45"></a>
</div>
<table align="center">
<tr>
<td align="center"><img src="assets/screenshots/home.png" alt="Home screen" width="230"><br><sub><b>Home</b> · today's workout and weight</sub></td>
<td align="center"><img src="assets/screenshots/workout.png" alt="Workout screen" width="230"><br><sub><b>Guided workout</b> · demos and sets</sub></td>
<td align="center"><img src="assets/screenshots/stats.png" alt="Stats screen" width="230"><br><sub><b>Stats</b> · heatmap, charts and PRs</sub></td>
</tr>
</table>
## Why openGym
Most workout apps keep your data on their servers, push you towards a subscription, or vanish
when the company does. openGym runs on your own box, keeps your data in a folder you control, and
is yours to fork. It still behaves like a modern app: installable on the home screen, passkey
sign-in, works offline, syncs between your phone and your laptop.
No account on someone else's server, no subscription, no ads, no telemetry. One
`docker compose up` and it's running.
The [in-browser demo](https://opengym.ch/demo/) is the real app with example data,
if you want to try it before installing anything.
## Features
**Planning**
- A routine per weekday over a library of **over 5,600 exercises** with animated demos, searchable and
browsable by muscle on a body map. Filter by the equipment you own.
- Four starter plans (Push/Pull/Legs, Upper/Lower, Full Body, 5×5) that load as ordinary,
editable routines.
- Move a session to another day without touching the weekly plan. The week starts on Monday or
Sunday, your choice.
- Or skip the weekdays altogether: a **rotation** (A, B, C, A, ...) where the next session is
simply the next one you haven't done, however the week went.
- Supersets, warm-up sets, drop sets and rest-pause, timed exercises (planks, hangs, carries),
cardio by time and speed, rest time per exercise, planned deloads.
- Your own exercises, with your own photo, GIF or short video. Location data is stripped on the
device before upload.
**Training**
- Guided sessions: today's workout starts itself, weights are pre-filled from last time, a rest
timer runs between sets, PRs are detected as you go. On a rest day it tells you when the next
session is.
- A quiet workout screen: one menu per exercise, the set number as the set's own menu, card or
list view. Switches in Settings bring the old button rows back if you liked them.
- Optional effort column as RIR or RPE, colour-coded, with a plain-language line per level.
- Plate math for barbell, EZ bar, trap bar and Smith machine, worked out from the plates you own.
- Bodyweight exercises know they carry no load: log reps, add a dip belt if you use one.
- Per-side reps for lunges and single-arm work, the screen stays awake while you train, and a
rest-timer alert can flash the screen for loud gyms.
- Swipe a set left to delete it (with Undo) or right to copy it. Pyramid sets with their own reps
and rest per set, and a scroll wheel for any rest time up to 15 minutes.
**Progress**
- Progression rules per routine or per exercise: linear, Greyskull LP, double progression through a
visible rep range, triple progression (reps, then sets, then load), or adding time. Each target explains why it is that number; missed reps never
add load, stalls trigger a deload.
- Estimated 1RM per exercise with its own curve, Structural Balance ratios (Poliquin, Thibaudeau,
ATG), a year-long activity heatmap.
- A muscle map in three modes: where your volume went, what is still recovering, and what has gone
untrained.
- Body-weight chart against a goal line, and progress photos on a timeline with a before/after
slider.
- Edit any saved workout after the fact, log one you did on paper, or move it to the right date.
Records are re-read from the corrected history.
**Accounts and data**
- Passkeys (Face ID, Touch ID, fingerprint) with per-profile data synced across devices. Password
sign-in can be switched on per instance; new devices pair with a one-time code or QR.
- Two devices editing at once merge field by field instead of overwriting each other (see
[sync](#how-sync-works)).
- Import from FitNotes, Strong, Hevy (CSV or API key) and Apple Health weight exports. Export
everything as one JSON file whenever you like.
- Share a plan as a small file or print it as a PDF.
- Optional admin dashboard with invite-only signup and an activity log.
- 19 languages, including right-to-left Arabic and Traditional Chinese. Exercise names and
instructions are translated for most of them.
**Optional extras, off by default**
- An [AI coach](docs/AI_COACH.md) that drafts a week of routines and later suggests changes based on
what you logged. You approve every change. It runs on your server with your own provider key
(Anthropic, OpenAI, Gemini or any OpenAI-compatible endpoint, Ollama included).
- An [MCP server](mcp/README.md) so an assistant like Claude Desktop can answer questions about your
training history. Read-only and local; not part of the Docker build.
The full list of what changed release by release is in the [changelog](CHANGELOG.md).
## Quick start
You need [Docker](https://docs.docker.com/get-docker/) with Compose.
```bash
git clone https://github.com/DuarteSantos8/openGym
cd openGym
cp .env.example .env
docker compose pull # prebuilt images, amd64 + arm64 (skip this to build from source)
docker compose up -d
```
Open <http://localhost:8080>, tap **Create profile**, and you're in. The exercise animations are
part of the image, so there's nothing else to download.
To reach it from your phone with passkeys you need HTTPS on a domain; that's a two-line change in
`.env`. The [self-hosting guide](docs/SELF_HOSTING.md) walks through Cloudflare Tunnel, Caddy,
Traefik and nginx, and there are separate guides for
[HTTPS on a LAN](docs/SELF_HOSTING_HTTPS.md) and [Kubernetes](docs/SELF_HOSTING_KUBERNETES.md).
> [!NOTE]
> Images are published from the same tag to `ghcr.io/duartesantos8/opengym-{api,web}`
> (what `docker-compose.yml` pulls) and `registry.gitlab.com/duartesantos8/opengym/{api,web}`. Swap
> the `image:` lines if you prefer GitLab's registry, or run `docker compose up -d --build` to build
> locally. Either way you don't need Node on the host.
<details>
<summary><b>Configuration reference</b> (all through <code>.env</code>)</summary>
| Variable | What it does | Default |
|---|---|---|
| `RP_ID` | Hostname passkeys are bound to | `localhost` |
| `ORIGIN` | Full URL the app is served from | `http://localhost:8080` |
| `WEB_PORT` | Host port for the web UI | `8080` |
| `NGINX_PORT` | Port the web container listens on inside the container | `80` |
| `BACKEND` | Name of the API service that `/api` is proxied to | `api` |
| `PORT` | Port the API listens on; the web container proxies to the same value | `3000` |
| `RP_NAME` | Name shown in the passkey prompt | `openGym` |
| `SESSION_DAYS` | How long a sign-in lasts, in days | `90` |
| `ADMIN_UIDS` | User ids that get the admin dashboard, comma-separated | *(none)* |
| `FIRST_USER_ADMIN` | `1`: the first profile created on an empty instance becomes its admin | *(off)* |
| `INVITE_ONLY` | Require an invite code to create a profile | *(off)* |
| `ALLOW_GUEST` | Offer "Continue without account"; `0` requires a profile | *(on)* |
| `PASSWORD_LOGIN` | Offer name-and-password sign-in next to passkeys | *(off)* |
| `TRUST_PROXY` | Let the sign-in throttle read the client address from proxy headers | `1` in `docker-compose.yml` |
| `AUDIT_LOG` | Record sign-ins and admin actions; `0` records nothing | *(on)* |
| `AUDIT_MAX` | Events kept in the activity log; `0` for no limit | `5000` |
| `AUDIT_DAYS` | Days kept in the activity log; `0` keeps until `AUDIT_MAX` | `90` |
| `AUDIT_IP` | Record the caller's address: `off`, `net` (network only) or `full` | `off` |
| `ALLOWED_PRIVATE_IPS` | Private addresses push endpoints may resolve to (FakeDNS, split-horizon DNS): comma-separated IPs, `a-b` ranges or CIDR blocks | *(none)* |
| `VAPID_SUBJECT` | Contact URL sent with push notifications | your `ORIGIN` |
| `API_TARGET` | API image to build: `default`, or `coach` with the Claude Agent SDK and Codex CLI | `default` |
| `COACH_DISABLED` | `1` forces the AI coach off instance-wide | *(unset)* |
Push-notification keys are generated on first run into `./data/vapid.json`. `DATA_DIR` is pinned to
`/data` inside the container and mapped to `./data` on the host; change the volume, not the
variable. The [self-hosting guide](docs/SELF_HOSTING.md) covers every option in detail.
</details>
## Phone app
The same codebase builds a standalone app with Capacitor: no account, no server, everything stays
on the phone, with native reminders and a rest countdown in the notification shade.
- **Android:** download the signed APK from the [latest release](https://github.com/DuarteSantos8/openGym/releases/latest)
or the [website](https://opengym.ch). Each build sits next to its `.sha256`, and
the app checks for updates itself. openGym is deliberately not on the Play Store.
- **iPhone:** Apple doesn't allow installs outside the App Store. Self-host and add the PWA to your
home screen from Safari, or build the native app onto your own device with Xcode.
Details and build instructions: [docs/MOBILE.md](docs/MOBILE.md).
## How it works
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/diagrams/architecture-dark.png">
<img src="docs/diagrams/architecture.png" alt="Architecture: the phone talks HTTPS to nginx (web), which serves the app and proxies /api to the Node api; the api stores JSON in ./data. The exercise media ship inside the web image; the AI coach and MCP server are optional." width="520">
</picture>
</p>
- **`frontend/`** is React 19 and Vite (React Router, Zustand), built to static files inside Docker.
- **`api/`** is plain `node:http` with two dependencies: `@simplewebauthn/server` for passkeys and
`web-push` for notifications. Everything is stored as JSON under `./data`.
- **`web/`** builds the frontend and serves it with nginx, proxying `/api` so the whole app sits on
one origin, which passkeys require.
The training logic (progression rules, 1RM, how a logged session is read back) lives in pure
functions under `frontend/src/lib/` with tests beside them. The HTTP API is documented as an
OpenAPI spec in [`api/openapi.yaml`](api/openapi.yaml), browsable at
[opengym.ch/api.html](https://opengym.ch/api.html).
### How sync works
Each profile's data is one document with a server revision. A device sends the revision it last
saw along with its changes; if another device wrote in between, the server refuses and returns the
current document so the device can merge and retry. Every change carries its own stamp, down to a
single setting or routine field, so the merge keeps the newest edit of each one and a deletion stays
deleted.
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/diagrams/sync-dark.png">
<img src="docs/diagrams/sync.png" alt="Sync: a device saves with the revision it last saw; if another device wrote in between, the server answers 409 with the current document, the device merges and saves again." width="560">
</picture>
</p>
Nothing that hasn't reached the server is discarded on disconnect or sign-out, and the app shows a
banner whenever it's working offline.
### Your data
Everything lives in `./data` on your host:
| File | Contents |
|---|---|
| `db.json` | Profiles and public passkey data |
| `db.json.bak` | A copy of the last good `db.json`, used if the main file can't be read |
| `state-<user>.json` | Each user's plan, workouts, body weight and settings |
| `audit.log` | Admin activity log (no IP addresses unless you turn that on) |
| `secret` | Session-cookie signing key |
Back up `./data` and you've backed up everything. Passkey private keys never reach the server; they
stay in your phone's secure hardware or your password manager.
## Documentation
The [documentation index](docs/README.md) sorts every guide by who it's for. The most used ones:
| I want to | Read |
|---|---|
| Get a quick answer | [FAQ](docs/FAQ.md) |
| Set up my own instance | [Self-hosting](docs/SELF_HOSTING.md) |
| Use the Android or iPhone app | [Phone app](docs/MOBILE.md) |
| Bring my history from another app | [Importing data](docs/DATA_IMPORTS.md) |
| Turn on the AI coach | [AI coach](docs/AI_COACH.md) |
| Contribute code | [Contributing](CONTRIBUTING.md) |
| Report a security problem | [Security](SECURITY.md) |
## Roadmap
A release roughly every two weeks, each small and themed. The full plan is in
[ROADMAP.md](ROADMAP.md), and the issues sit in the
[GitHub milestones]