A self-hosted strength-training app with an AI coach that programs for you and holds you accountable.
Plan and log your lifts set-by-set, track PRs, estimated 1RMs, tonnage and consistency over time β and train alongside an AI coach that knows your history, respects your injuries, and emails you when you skip a session.
AI Coach Β Β·Β Notifications Β Β·Β Dashboard Β Β·Β Quickstart Β Β·Β Features
|
Programs your next session, remembers your injuries and goals, and nudges you when you slip. |
PRs, estimated-1RM, tonnage and consistency β every metric derived from the sets you actually log. |
One |
Chat with a Starting-Strength coach that has your full training history in context. He remembers your injuries and weight caps, adapts programming around them, schedules your sessions, and won't let you push into a bad idea.
Above, Hank hears that a knee's been cranky, logs it to memory (the green pill), pulls the athlete's real last sessions and 405 lb deadlift goal from context, and programs a knee-friendly day with the reasoning spelled out β then offers to write it straight onto the calendar.
Every coach is editable β its name, voice, and coaching philosophy are sent with every message. Tell it what to remember (injuries, caps, goals), and it shapes every answer and every programmed session.
![]() |
![]() |
| The persona / βsoulβ β fully editable, per user | Long-term memory the coach reasons from |
A morning digest and missed-workout nudges, delivered over email, ntfy push, or both β at your local send time, with quiet hours and a per-channel test button.
Consistency heatmap, stacked tonnage-by-exercise, and an estimated-1RM strength trend β all range-filterable (30d β all time).
A scrollable month view (infinite scroll + month/year jump) and a list
view, with completion dots, coach-programmed badges, and
![]() |
![]() |
| Month calendar β completion dots + travel days | Day view β per-set logging, warm-ups, comments |
A searchable catalog with demo videos, per-exercise stats & history, and full create / edit / delete (delete is guarded when an exercise has history).
- AI Coach ("Hank") β chats with your full history in context; remembers
facts about you (injuries, caps, goals). Runs on Claude via Portkey. He can:
- schedule & adjust workouts from chat, respecting your weight caps and excluded lifts,
- autonomously program upcoming Starting-Strength sessions, progressed from your real lifts,
- honor blackout days (travel) β never programs or nags on them,
- send a morning digest + missed-workout reminders over email (Resend) and/or ntfy push, at each user's local send time, with per-channel "send test" buttons, quiet hours, and no duplicate sends across restarts.
- Calendar β infinite-scroll month view + list view with completion dots and travel markers.
- Workout logging β exercise cards (A / B / Cβ¦),
N Γ reps @ weight, expandable per-set logging, warm-up calculator, demo links, comments, skip, and Mark Complete. - PRs & Estimated 1RMs β auto-computed per workout (Epley formula).
- Stats & history β per exercise: working-weight, e1RM and PR lines with 30dβ¦all ranges.
- Dashboard β consistency heatmap, tonnage-by-exercise, strength-trend charts.
- CSV import β bring in workout-history exports; completed sessions without per-set actuals fall back to the prescription so they still feed PRs and charts.
- Multi-user + admin β email/password auth, per-user data isolation, an admin area to create users and manage roles, and a separate coach per user.
Every user sees only their own workouts and gets their own coach (cloned from
a shared "Hank" template β personas can diverge). Admins get an /admin area to
create users, set roles (admin / user), reset passwords, and edit any user's
coach. Auth is a lightweight custom email + password with database sessions β no
external service, so it runs fully self-hosted.
- Next.js 16 β App Router, React 19, Server Components + Server Actions
- Tailwind CSS v4 Β· Recharts Β· lucide-react Β· date-fns
- Drizzle ORM on Postgres via the standard
node-postgresdriver β runs against any Postgres (local Docker, self-hosted, managed) - Claude via Portkey for the coach Β· pluggable notifications (Resend email, ntfy push) Β· node-cron for the daily tick (catch-up + persistent dedupe)
- bcryptjs + DB sessions for auth
Requires Docker β that's it.
git clone https://github.com/dstahl11/StrongCoachAI && cd StrongCoachAI
docker compose up -dThat brings up Postgres, applies the schema, creates an admin account, and starts the app at http://localhost:3000. Grab the generated admin password from the init logs (you'll be asked to change it at first sign-in):
docker compose logs init | grep -A2 "admin account"Optional knobs β set as environment variables (or in a .env file next to
docker-compose.yml) before docker compose up:
| Variable | Effect |
|---|---|
ADMIN_EMAIL / ADMIN_PASSWORD |
choose the first admin's credentials instead of generated ones |
SEED_DEMO=1 |
load ~16 weeks of demo training history (pairs with TODAY_OVERRIDE=2026-06-27) |
CRON_SECRET |
enables POST /api/coach/tick for an external cron (the built-in scheduler runs regardless) |
PORTKEY_API_KEY + PORTKEY_INTEGRATION |
turn on the AI coach (chat + personalized notification copy) |
RESEND_API_KEY + COACH_FROM_EMAIL |
turn on the email notification channel |
NTFY_SERVER |
default ntfy server for push notifications (default https://ntfy.sh; topics are per-user in Settings) |
TZ |
container timezone (users can also set a per-user timezone in Settings) |
Everything optional stays dormant when unset β the app runs fine without any of it, and the startup log prints a friendly summary of what's configured.
Requires Node β₯ 20.9 (pinned to
22.14.0via.nvmrcβ runnvm use).
npm install
docker compose up -d db # just the database
cp .env.example .env.local # then fill in keys as needed
npm run db:push # sync schema
npm run db:seed # optional demo history
npm run db:create-admin -- you@example.com 'your-password' 'Your Name'
npm run dev # http://localhost:3000| Script | What it does |
|---|---|
npm run dev |
Start the dev server |
npm run build |
Production build + typecheck |
npm run db:push |
Sync the Drizzle schema to Postgres |
npm run db:seed |
Wipe + reseed demo training history |
npm run db:create-admin -- <email> <pw> [name] |
Create an admin and assign existing data |
npm run import -- <csv> [--append] [--user=<id>] |
Import a workout-history CSV |
npm run db:studio |
Open Drizzle Studio |
users own everything. exercises is a shared catalog; workouts β
workout_exercises β set_groups (prescriptions) and logged_sets (actual
performance). PRs, e1RM, tonnage and the charts are all derived from
logged_sets. The coach adds coach_profile, coach_memories, chat_messages,
blackout_days, and coach_events. See src/db/schema.ts.
Delivery is a small pluggable notifier interface (src/lib/notify/) with
email + ntfy backends β adding Pushover/Telegram/webhooks later is a ~50-line
backend plus a settings card. The daily tick runs in-process every 15 minutes
with catch-up semantics: each user fires once their local hour reaches
their configured send hour, dedupe is persisted per-channel in coach_events
(failed sends retry a few times; restarts never double-send), and quiet hours
suppress late catch-ups. Admins get a "Run check now" button and every user sees
a recent-notifications log in Settings. Prefer an external scheduler? Set
COACH_CRON=0 and point a system cron at POST /api/coach/tick with your
CRON_SECRET.
- Deploys as a Docker container with a Postgres container alongside it
(see
Dockerfile+docker-compose.yml; theinitservice makes first-run and upgrades idempotent). - The coach explicitly respects stated injury limits and defers pain to a professional β it is not medical advice.







