GymCoach
The self-hosted workout tracker with an AI coach that actually knows your training.
Log every set, see what is working, and get weekly coaching from Claude or any OpenRouter model - on your own server, with your own key. Free, open source, no subscription.
▶ Live demo (login [email protected] / gymcoachdemo) ·
Self-host in one minute ·
Features ·
How this repo maintains itself
▶ Watch the 90-second presentation (with sound) · an AI coach, built by an AI
![]() Fast logging · offline-first |
![]() Dashboard · the one signal that matters today |
![]() Progress · e1RM, volume, muscle map |
![]() AI programs · from one sentence |
Why GymCoach
Looking for a self-hosted alternative to Strong, Hevy or Fitbod? GymCoach is a complete training tracker you run yourself, plus something self-hosted trackers rarely have: an AI coach grounded in your own numbers, running on the LLM key you choose.
| | GymCoach | Typical cloud app | Typical self-hosted tracker | | ---------------------------------------------------------- | :------: | :---------------: | :-------------------------: | | Your data in your own database | ✅ | ❌ | ✅ | | AI coach that reads your sessions and explains why | ✅ | 💰 subscription | ❌ | | Established programs as written (5/3/1, GZCLP, nSuns, PPL) | ✅ | partial | partial | | Cardio from watch files (FIT, TCX, GPX), no cloud account | ✅ | ❌ | ❌ | | Offline-first logging, installable (PWA) | ✅ | ✅ | varies | | Full export, nothing paywalled | ✅ | varies | ✅ | | ChatGPT / MCP connector | ✅ | ❌ | ❌ |
With no AI key set, GymCoach is still a clean, fast tracker. Add a key later to turn on the coach, the chat and program generation.
Self-host in one minute
All you need is Docker on an x86_64 host (arm64 images are tracked in #411). The prebuilt image is pulled from GHCR, so there is no clone and no build; migrations run on start.
mkdir gymcoach && cd gymcoach
curl -fsSLo docker-compose.yml \
https://raw.githubusercontent.com/gymcoach-app/gymcoach/main/docker-compose.selfhost.yml
printf 'JWT_SECRET=%s\nPOSTGRES_PASSWORD=%s\n' \
"$(openssl rand -base64 48)" "$(openssl rand -hex 16)" > .env
docker compose up -d
Open ANTHROPIC_API_KEY=... (or LLM_PROVIDER=openrouter with OPENROUTER_API_KEY
and OPENROUTER_MODEL) to .env and run docker compose up -d again. Every
option is documented at the top of
docker-compose.selfhost.yml; reaching the app
over plain HTTP from another machine needs SESSION_COOKIE_SECURE=false (Safari
may need it even on localhost). Run the printf line only once and keep .env
with your backups: it holds the database password.
If GymCoach is useful to you, **a ⭐ on the repo is the best way to help other lifters find it.**
This repo largely maintains itself
GymCoach is also an experiment in autonomous software development. Claude Code agents maintain a real application through documented loops: select an issue, implement it on a branch, run the checks, open a PR, review it independently, and merge eligible changes through a gate that requires green CI and a structured review record for the exact commit. Agents also propose most of what gets built: research on competing apps and their users' requests seeded the roadmap, and a bounded ideation loop files a few feature issues when the backlog runs dry. A human sets the product direction and goals, and keeps the decisions reserved by the autonomy charter.
The feedback comes from running software: unit and integration tests, Playwright E2E tests, a production Docker smoke test, and independent reviews. Runs leave a public journal; recurring mistakes become lessons and changes to the procedures. The AI coach inside the app and the agents developing the repository are separate systems. Self-hosting GymCoach does not start a development agent.
Next experiment: measure whether the maintainer itself improves. The roadmap covers durable execution, enforced budgets, replayable development tasks, and evaluated changes to the agent's skills. These are planned capabilities, not a claim that the agent already improves itself reliably.
Evidence from real runs: measured outcomes, the controls now enforced in code, and reviews that caught real problems, with links.
How autonomous development works · Implementation roadmap · Run journal · Baseline report · Lessons from failures
Features
Everything below ships in the box. At a glance: a fast logger, real progress analytics, first-class cardio with watch-file import, and an AI coach that runs on your own key - all self-hosted.
Log and train
- Fast set logging - sets, reps, RIR, warm-ups and drop sets, with shorthand
100x8@9) and natural-language entry parsed by the AI.
- In-logger tools - a rest timer, a plate-loading calculator that draws the
- Rest timer controls - pause, -15/+15 s, and an optional screen flash when
- A weight picker that knows your gym - the weight and reps fields open a
- Last done, at a glance - each exercise card in the live session says when
- Pick your table metrics - the live sets table shows estimated 1RM,
- Double-progression suggestions - the next working load is computed from
- Supersets - pair exercises in the builder and run them A1/A2 with grouped
- Fix a set without deleting it - the sets you have already logged stay on
- Jump around a session - a strip of the workout's exercises shows what is
- Change the plan mid-session - an actions menu on the exercise card
- Readiness check-in - an optional pre-session soreness/readiness prompt that
- Return-to-training calibration - after a real layoff, the first session
- Your actual gym, not a generic one - describe the concrete stations and
- Quality-of-life - kilograms or pounds per user, an interface in English,
Track progress
- Strength trends - estimated-1RM and max-load over time, plus a per-exercise
- Volume and frequency - weekly volume per muscle group with MEV/MRV landmark
- Muscle heat map - front and back body silhouettes tinted by last week's
- Records and consistency - an all-time records board, personal-record badges
- Workout history on a calendar - page through the months, see which days you
- Copy a workout as text - one tap puts a finished session's recap on the
- Auto-regulation - stalled-lift detection and a deload recommendation from
- Goals and body comp - per-exercise goals (weight x reps) with a progress
- Progress photos - upload photos alongside your body metrics and compare
- Home dashboard - a coach-insight card surfaces the single most important
Cardio and wearables
- First-class cardio - log duration and distance (not weight x reps); a
- Watch-file import, no cloud - bring activities in as **TCX, GPX or Garmin
AI coach (bring your own model)
- Weekly debrief and adjustments - evidence-based, aware of your goals,
- Conversational coach - streaming chat grounded in your training data,
- Program generation - a full program from a one-sentence goal, editable
- Explainable by design - a "What your coach sees" card shows the exact
- Your provider - Anthropic SDK or any OpenRouter model. With no key set,
Programs and exercises
- Built-in templates - 5/3/1 BBB, GZCLP, nSuns, PPL, Upper/Lower, Starting
block column. A block is the row's
position among the workout's copies of that lift, so reordering, removing
or replacing one of those rows re-assigns sets logged before.
- Exercise catalog - searchable by name, on top of your custom exercises and
- Duplicate and reuse - copy a whole program or a single workout as a
- Printable workout sheet - print (or save as PDF) a clean A4 sheet per
Your data, your server
- Self-hosted - your training lives in your own Postgres; the AI runs on your
- ChatGPT / MCP connector - external agents can analyse your training data
- Import and export - bring history in from a Strong or Hevy CSV, or from
Stack
- Frontend: Next.js 15 (App Router), TypeScript strict, Tailwind CSS, Shadcn UI
- Backend: Next.js API routes, Prisma ORM, PostgreSQL 16
- AI: pluggable LLM provider (Anthropic SDK or OpenRouter)
- Infra: Docker and Docker Compose
Principles
A few beliefs shaped GymCoach:
- Your training data is yours. It lives in a Postgres database you control, not on someone else's servers. No ads, no tracking, no account you cannot delete.
- AI should be optional and yours to pay for. The coach runs on your own Anthropic or OpenRouter key, so there is no subscription and no rate-limited "free tier". With no key set, the app is a clean, fast tracker.
- Coaching should be grounded in your numbers, not generic advice. The AI only ever sees a structured summary of your own sessions, program and progress.
- Evidence over hype. Load progression uses double-progression logic, and the coach is prompted to reason from your data (and cite the usual names: Schoenfeld, Helms, Israetel) rather than invent.
- Self-hosting should be boring: one Docker Compose file, one database, standard Next.js.
How it works
The app:
- Next.js 15 (App Router) serves both the UI and the API routes; data lives in PostgreSQL through Prisma. Auth is a signed JWT in an httpOnly cookie; every record is scoped to a user id and every route checks ownership.
- The session logger is offline-first: each set is written to IndexedDB (Dexie) first for instant feedback, then synced to the server in the background, so a flaky gym connection never blocks you. A Wake Lock keeps the screen awake during a session.
- Progress is computed server-side: estimated 1RM (Epley), max load over time, and weekly volume per muscle group, with bodyweight-aware tonnage for movements like pull-ups and dips.
- A single provider interface (
lib/llm) sits in front of the Anthropic SDK, any OpenRouter model, or an OpenAI Responses-compatiblecodex-lbendpoint. You pick one with theLLM_PROVIDERenv var; the rest of the app does not care which. - For every AI call the server builds a compact, structured payload (your profile + recent sessions + active program + per-exercise progression) instead of dumping raw rows, then:
- The stable system prompt is marked for prompt caching, so multi-turn chats reuse it instead of re-sending it every turn.
See the AI in action
| Chat coach | Weekly debrief + 1-tap adjustments | Program generation |
![]() |
![]() |
![]() |
These clips use the built-indemoprovider (canned responses, no key). PointLLM_PROVIDERat Anthropic, OpenRouter, orcodex-lbfor the real thing.
Requirements
- Node.js 20+
- Docker and Docker Compose
- npm
Development setup
Recommended setup: Postgres in Docker, Next.js running locally for hot reload.
# 1. Environment variables
cp .env.example .env
Edit .env (the example ships with working dev defaults)
2. Install dependencies
npm install
3. Start Postgres
docker compose up -d db
4. Apply Prisma migrations
npm run db:migrate
5. Seed demo data (account + exercise catalog + program + sample session)
npm run db:seed
6. Start the dev server
npm run dev
The app runs on http://localhost:3030. Postgres is exposed on localhost:5433 on the host.
The demo account credentials come from .env (USER_EMAIL and USER_PASSWORD); the seed hashes the password at runtime.
Configuration
All configuration is done through environment variables. See .env.example for the full list (database, JWT secret, demo account, and the AI provider keys).
Progress photos / uploads
Progress photos are stored as plain files on the server's local filesystem,
under UPLOADS_DIR (default ./uploads, gitignored) - no cloud, no third
party. Images are served only through an ownership-scoped API route, never as
a public static path. Files are written owner-only (0o600) inside per-user
directories created 0o700, and every stored path is re-resolved (symlinks
included) inside the uploads dir before it is read, written or deleted. If you
self-host with Docker, mount that directory as a volume and include it in your
backups alongside the database.
Testing
Three tiers: unit/component (Vitest + jsdom), integration (Vitest against a real Postgres), and end to end (Playwright driving the built app).
npm run test # unit and component tests
npm run test:coverage # with coverage report
Integration + E2E use a dedicated Postgres (docker-compose.test.yml, port 5434):
docker compose -f docker-compose.test.yml up -d
DATABASE_URL=postgresql://gymcoach_test:gymcoach_test@localhost:5434/gymcoach_test \
npx prisma migrate deploy
npm run test:integration
npm run build && npm run test:e2e
docker compose -f docker-compose.test.yml down
CI (.github/workflows/ci.yml) runs lint, typecheck, unit, integration,
build and E2E on every push and pull request.
Scripts
| Script | Description |
| --------------------- | ------------------------------------------ |
| npm run dev | Next.js dev server (port 3030) |
| npm run build | Production build |
| npm run start | Run the production build |
| npm run lint | ESLint |
| npm run typecheck | TypeScript type checking |
| npm run test | Unit and component tests |
| npm run test:e2e | End to end tests |
| npm run format | Prettier |
| npm run db:migrate | Apply migrations (dev) |
| npm run db:reset | Reset the database (drop + migrate + seed) |
| npm run db:seed | Load the demo dataset |
| npm run db:studio | Open Prisma Studio |
| npm run db:generate | Regenerate the Prisma client |
Project layout
.
├── app/ # Pages and API routes (App Router)
├── components/ # React components (Shadcn UI in components/ui)
├── lib/ # Helpers (db, auth, stats, llm, etc.)
├── prisma/ # Schema, migrations and seed
├── public/ # Static assets (PWA icons, manifest)
├── tests/ # Integration (Vitest) and E2E (Playwright) tests
├── docs/ # Project documentation
└── docker-compose*.yml
Deployment
The simplest path is Self-host in one minute:
docker-compose.selfhost.yml runs the prebuilt image and applies migrations on
start. Put it behind a reverse proxy (Nginx, Caddy, Traefik) for HTTPS and set
MCP_PUBLIC_URL to your public URL so MCP connector links point at it.
To build from source instead, docker-compose.prod.yml builds the image locally (app + Postgres):
cp .env.example .env
Fill in real values (JWT_SECRET, the AI provider key, NEXTAUTH_URL, ...)
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml exec app npx prisma migrate deploy
Pulling the prebuilt image
Every main commit whose CI run is green publishes a linux/amd64 image to
GHCR, so you can pull instead of building on your own hardware, and registry
watchers (Watchtower, Diun) have digests to compare (a commit whose CI fails
is never published):
ghcr.io/gymcoach-app/gymcoach:latest- moving tag, followsmainghcr.io/gymcoach-app/gymcoach:sha-- immutable, one per commit
gymcoach-app organization on 2026-10-05. Images
published before that live at ghcr.io/julien-au/gymcoach and no longer
receive updates: point an existing install at ghcr.io/gymcoach-app/gymcoach
(nothing else changes, your volumes are kept).
docker-compose.selfhost.yml uses it out of the box. With
docker-compose.prod.yml, replace the build: block of the app service with
image: ghcr.io/gymcoach-app/gymcoach:latest (that file keeps build: as its
default so existing setups and forks keep working unchanged).
Deploying a public demo instance
Set NEXT_PUBLIC_DEMO_MODE=true (plus the throwaway demo credentials) in the
instance's .env, then build with the demo profile and run the one-shot
seeder. It fills the demo account with a rich deterministic dataset (12 weeks
of sessions, a bodyweight trend, a goal, readiness check-ins); re-running it
on every deploy also resets whatever visitors changed.
docker compose -f docker-compose.prod.yml --profile demo up -d --build
docker compose -f docker-compose.prod.yml --profile demo run --rm seed-demo
The public demo is a single shared account, so visitors who start a session pollute it. Re-run the seeder on a schedule (e.g. a cron every 30 minutes) so the demo always opens on the clean, populated state - it wipes and recreates the demo account's data without touching the rest.
To keep the deployed demo on the latest code, prefer a pull-model cron on the
host (periodically git fetch and rebuild only when the branch moved) over an
inbound SSH deploy from CI - a host reaching out to GitHub is far more reliable
than CI reaching in to a small VPS.
Roadmap
- [x] Single user MVP (logging, progress, weekly AI debrief, program adjustments)
- [x] Pluggable LLM provider (Anthropic SDK or OpenRouter, switchable via env)
- [x] Multi user support (registration, profiles, data isolation)
- [x] AI program generation from a natural language goal
- [x] Conversational AI coach (streaming chat with your training context)
- [x] Test pyramid (unit, integration, E2E) and CI
- [x] Shorthand set logging (
100x8@9) - the deterministic slice of
- [x] In-session AI suggestions (ask the coach mid-workout with the live
- [x] Free-text (AI-parsed) set logging (opt-in "Parse with AI" fills the set
- [x] Progress photos (local-only upload with side-by-side compare)
- [x] Interface localization (English, French, Russian and German, extensible message
- [x] Muscle heat map (body silhouettes tinted by weekly volume vs MEV/MRV)
- [x] Physical gym equipment inventory, with the equipment used recorded on
- [x] Return-to-training calibration after a long layoff
- [x] Printable A4 workout sheet (blank weight / reps / RIR cells)
- [x] Calendar workout history (month grid, day drill-down, program filter, your
- [x] Live-session exercise navigation (jump strip plus a per-exercise detail
- [x] Inline set editing during a live session (correct a logged set in place,
- [x] Gym-aware weight picker and in-session exercise actions (replace, add,
- [x] MCP gym inventory, equipment backfill, bodyweight update and capability
- [x] Favourite exercises, duplicate a program or workout, and save a finished
- [x] Repeated-exercise blocks (5/3/1 BBB) and a "last done" day in the live
Contributing
Contributions are welcome and encouraged. See CONTRIBUTING.md for setup, conventions, the test commands, and - since this repo is largely maintained by an autonomous AI loop - an honest description of how external issues and PRs are reviewed and merged ("How your contribution is handled"). Notable changes are tracked in the CHANGELOG.
Thanks
- @SHAREN - first external contributor, and a
.gitignore bug
that shadowed an API route in fresh clones. Then a second series:
return-to-training calibration (#311), the physical gym equipment inventory
(#312) and the equipment recorded on each logged set (#313). They also
proposed making MCP a first-class "external deep coach" interface (#331); the
sequencing is answered on that issue, and its one web-app-only piece is
tracked as #333. A third series followed in September, seven PRs, all merged:
portable progress-photo paths (#350), locale-stable MCP dates (#354), the
calendar workout history (#351), the PWA update refresh (#352), reliable
locale switching behind a proxy (#353), live-session exercise navigation
(#355) and inline set editing (#356) - the last of which they hardened
themselves, closing every finding of the review within hours. A fourth
series of eight PRs, all merged on 2026-09-30, brought the barbell
diagram (#366), table metric columns (#367), the gym-aware weight picker
(#364), in-session exercise actions (#363), in-session equipment weight
editing (#365), and three MCP additions: the audited equipment backfill
(#362), the gym inventory tools (#368) and the capability index (#369).
- @Retsumdk - fixed the silent weight-stack
- @f1dot4 - first contribution: updating the
- @shaurya703 - picked up three of the loop's
- @mvnixon - reported the
GET /mcphang that
- @gogoku121 - proposed the browsable
- Exercise technique photos come from
License
MIT, see LICENSE.






