Getting started with Gym
Clone the source, configure PostgreSQL, sessions, and S3 storage, apply migrations, create the first operator, run onboarding, and deploy.
Requirements
- Node.js.
package.jsondeclares>=20.0.0; Next.js 16 needs 20.9 or later. - npm (the repository ships a
package-lock.json). - A PostgreSQL database you are allowed to migrate.
DATABASE_URLmust start withpostgres://orpostgresql://. - S3-compatible object storage for images (member profile photos) in production.
Hosted app
The hosted app is at gym.openship.org. The staff dashboard is at /dashboard on the same host.
Install
git clone https://github.com/openshiporg/openfront-gym.git
cd openfront-gym
npm install
cp env.example .envThe example file is named env.example (no leading dot).
Environment
features/keystone/index.ts runs in strict mode when NODE_ENV=production or GYM_STRICT_CONFIG=true. In strict mode it refuses to start without DATABASE_URL, a SESSION_SECRET of at least 32 characters, and all five S3 values. Outside strict mode it falls back to a local database URL, a test session secret, and placeholder S3 values.
DATABASE_URL=postgresql://user:password@127.0.0.1:5432/openfront_gym
SESSION_SECRET=replace-with-at-least-32-random-characters # openssl rand -hex 32
S3_BUCKET_NAME=your-bucket
S3_REGION=your-region
S3_ENDPOINT=https://your-s3-compatible-endpoint
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-secret-access-keyOptional settings read by the code. Each optional capability stays off when its settings are missing or incomplete; strict mode logs a warning instead of failing.
| Variable | Used for |
|---|---|
NEXTAUTH_URL | Public site origin. Needed for Stripe return URLs and links in email. NEXT_PUBLIC_BACKEND_URL is read as a fallback. |
INTERNAL_APP_URL, INTERNAL_GRAPHQL_URL | Private origin for server-to-server GraphQL calls. INTERNAL_APP_URL wins when both are set. |
CLIENT_SOURCE_TRUST_PROXY | Set true only if your proxy appends the client address to X-Forwarded-For. Used by rate limits. |
STOREFRONT_ORGANIZATION_ID | Binds the public storefront to one organization. Required when more than one organization exists or when member signup is on. |
PUBLIC_MEMBER_SIGNUPS_ALLOWED, PUBLIC_MEMBER_SIGNUP_ORGANIZATION_ID | Public member signup. Off by default. The signup organization must match STOREFRONT_ORGANIZATION_ID. |
PUBLIC_SIGNUPS_ALLOWED | Keep false. Dashboard self-signup is refused either way; true only logs an error. |
MAIL_ENABLED, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, CONTACT_FORM_TO | Password reset and email-change mail, staff copies of contact messages. Mail also needs SMTP_HOST and SMTP_FROM. In production nothing is sent unless MAIL_ENABLED=true; outside production mail is on unless MAIL_ENABLED=false. Links in email use NEXTAUTH_URL. |
STRIPE_ENABLED, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Membership checkout and counter card payments. STRIPE_ENABLED=true allows new charges. Checkout is hosted by Stripe, so no publishable key is read by the current code. |
STRIPE_USE_ENVIRONMENT_PROVIDER, STRIPE_ACCOUNT_ID | Take the Stripe provider for STOREFRONT_ORGANIZATION_ID from the environment. Onboarding copies STRIPE_ACCOUNT_ID onto the provider record it creates. |
PAYMENT_TEST_MODE, PAYMENT_TEST_WEBHOOK_SECRET | Isolated payment tests only. Strict mode refuses to start with PAYMENT_TEST_MODE=true. |
QR_CODE_SECRET | Rotating member check-in QR codes (32+ characters). Without it, codes are unavailable. |
KIOSK_API_TOKEN, KIOSK_ORGANIZATION_ID | Staff-unlocked kiosk at /kiosk/check-in. Both are required. |
DISCOVERY_API_KEY, DISCOVERY_ORGANIZATION_ID, DISCOVERY_API_SCOPES, DISCOVERY_PARTNER_ALLOWLIST | Environment-configured partner discovery API. Credentials can also be issued from the Operations page. |
OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENS | Dashboard AI chat (/api/completion). |
Keep .env out of source control. Generate each secret separately.
Migrations and local run
npm run devnpm run dev runs keystone build --no-ui, applies checked-in migrations with prisma migrate deploy, and starts Next.js. To change the schema, run npm run migrate:gen against a disposable database. It rebuilds Keystone, regenerates the dashboard view order, and runs keystone prisma migrate dev.
npm run dev applies migrations to whatever DATABASE_URL points at.
Once running:
- Storefront:
/ - Member account:
/account - Staff dashboard:
/dashboard - Kiosk:
/kiosk/check-in(after kiosk configuration and staff unlock) - GraphQL API:
/api/graphql - Health check:
/api/health(runsSELECT 1against the database)
Other scripts: npm run lint, npm run typecheck, and npm run test:offline (runs tests/*.test.ts).
First operator
On an empty database, every route redirects to /dashboard/init. That page creates the first user with name, email, and password. The user is attached to the gym_default_organization organization, which the tenant migration creates, and gets an Admin role with full management capabilities.
Dashboard self-signup is refused. Add staff from the dashboard and give them a role. Members join through invitation (/dashboard/platform/members), contact import (/dashboard/platform/imports), or public signup when it is enabled.
Onboarding
Open the onboarding dialog from the dashboard sidebar. It offers three templates:
- Complete Starter Setup (
full): two membership plans, six class types, three instructors, fifteen weekly schedules. - Basic Starter Setup (
minimal): one plan, one class type (Yoga), one instructor, three weekly schedules. - Custom Setup (
custom): starts from the full configuration and lets you edit plans, classes, instructors, and schedules before it is validated.
All three also create (or reuse) store settings, one location, a bounded instructor role, and a Stripe provider record. The Stripe record is marked installed only when STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are set. Setup creates no members, charges, subscriptions, or revenue.
After setup, work through the readiness checklist it returns. Required steps: store settings (name, contact email, hours), an open location, membership plans, class types, active coaches, and a weekly schedule with upcoming sessions. When checkout is on, at least one plan also needs a Stripe product and price ID.