Workout.cool: running your own fitness planner on Postgres, Prisma and a CSV exercise dump
🏋 Modern open-source fitness coaching platform. Create workout plans, track progress, and access a comprehensive exercise database.
At a glance
- What is it?
- Self hosted fitness coaching in TypeScript: a Next.js app over PostgreSQL 15 with Prisma migrations, a thirteen column CSV importer for the exercise library, and env keys for Stripe, Google sign-in, analytics and mail that you fill in yourself. Worth a look if you want your plans and history on your own database, and if you can live without a phone app or a bundled video library.
- Who is it for?
- Pick workout-cool if you want your workout plans and history inside a database you control, and if you are willing to finish the configuration yourself: the database name in the environment template and the one in the manual setup command differ, the billing, OAuth, analytics and mail keys are all empty, and exercise video URLs are yours to supply.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 15 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
make dev does four jobs before Next.js answers
The Docker path here is a Makefile target rather than a compose file you drive yourself. Copy the environment template, then one command does everything:
git clone https://github.com/Snouzy/workout-cool.git
cd workout-cool
cp .env.example .envmake devThat target starts the database in Docker, runs the migrations, seeds the database, and only then starts the Next.js development server. Teardown is a second target, `make down`. Behind those targets the compose file declares two services. `postgres` runs the `postgres:15` image, publishes `${DB_PORT:-5432}:5432`, and keeps its data in a named volume called `pgdata`, so a stop and start cycle keeps your plans and history. The second service, `workout_cool`, builds from the repository Dockerfile and publishes `${APP_PORT:-3000}:3000`. It declares `condition: service_healthy`, so it waits for a healthcheck that runs `pg_isready` against the database named in your .env every five seconds, five times before giving up. Point a browser at http://localhost:3000 and the app is there.
The template and the createdb command disagree on the database name
The manual path has a mismatch you have to settle yourself. The environment template sets POSTGRES_DB=workout-cool and writes a connection string ending in the same hyphenated name, with username and password left as literal placeholders. The manual setup instructions tell you to create a database with an underscored name instead:
pnpm install
createdb -h localhost -p 5432 -U postgres workout_cool
npx prisma migrate dev
pnpm devNothing in those steps reconciles the two spellings, and the compose healthcheck reads ${POSTGRES_DB} straight out of the .env file you copied. Follow both literally and you end up with a database that your app never points at, which means migrations have nothing to migrate and the healthcheck never reports ready, so the service that waits on it never starts. Fix it in your own file: make the connection string name the database you actually created, and replace the placeholder auth secret with a value you generate using openssl rand -base64 32 before anything else touches your data.
The container entrypoint is a setup.sh the README does not document
The production image does not start with a plain server command. It marks scripts/setup.sh executable, sets `ENTRYPOINT ["/app/scripts/setup.sh"]`, and pairs it with `CMD ["pnpm", "start"]` on port 3000. The runner stage copies public, the built .next directory, node_modules, package.json, the prisma directory, data and scripts out of the builder, so the boot behaviour lives in that one script. The builder copies .env.example over .env before running the build, which means the image is compiled with the template's placeholder values rather than the ones you supply at run time. What setup.sh contains is not spelled out anywhere in the README. If it applies migrations, seeds rows, or waits for the database, none of that is available to you as a command you can copy into a deployment script. You cannot reproduce the container's boot sequence from the written steps alone, so read that file before you point it at data you care about.
Thirteen CSV columns, six of them English twins
The exercise library arrives as a flat file, and the importer is picky about its shape. The header the project expects is one line of thirteen fields:
id,name,name_en,description,description_en,full_video_url,full_video_image_url,introduction,introduction_en,slug,slug_en,attribute_name,attribute_valueThe script behind that header is scripts/import-exercises-with-attributes.ts, exposed as import:exercises-full, and it takes the file path as its last argument:
pnpm run import:exercises-full /path/to/your/exercises.csv
pnpm run import:exercises-full ./data/sample-exercises.csvThe second form is also what the db:seed shortcut runs, so the sample data in the data directory is the fastest way to see populated screens. The sample row that ships in the repository is numbered 157 and its name field is written in French, which shows the paired columns are load bearing rather than decorative. What the README does not document is how many attribute rows one exercise needs, what happens on a duplicate id, or how to reverse a bad import, and the script name offers no dry run flag. Get the column set right before the first run, not after.
Stripe price IDs, Google credentials and OpenPanel keys are all yours to fill in
The application ships with a long environment file, and most values only matter once you switch the matching feature on. Billing is labelled optional and expects a secret key, a webhook secret, a publishable key, and six price IDs split into monthly and yearly variants for the EU, the US and LATAM. Sign-in runs on BETTER_AUTH_URL and BETTER_AUTH_SECRET, with Google OAuth values read from GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET under the email and profile scopes. Analytics wiring takes an OpenPanel secret key and a client id. Mail leaves over SMTP, and the template suggests MailHog on port 1025 for local work, with the sender address set to a workout.cool mailbox and SMTP_SECURE left false. SEED_SAMPLE_DATA=true is the switch that fills a fresh database with sample content. Every one of those lines holds a placeholder today, so each feature that depends on one stays dark until you replace the value.
APK searches land on a web app with no native client
Most public interest in this name is about putting something on a phone: an APK download, an Android build, a free app install. What the repository holds is a web application served from https://workout.cool, with app/, src/, prisma/, public/, data/ and a components.json file at the top level, and no Android or iOS project anywhere in that listing. The only mobile signal is one dependency, @better-auth/expo, which provides Expo friendly authentication helpers for a client that does not live in this repository. The consequence is plain: if you came for an installable app file, this repository will not produce one, and an auth helper package does not change that. Self hosting gives you a browser based app on a domain you control, reached from a phone through the browser if you like. The client side stack itself is a set of primitives: Radix React components, drag and drop packages from dnd-kit, form resolvers, and Prisma client 6.5 or later as the data layer.
Commits land on main while tags stop at v1.3.2
The repository is not archived and the last push to the default branch main is dated 2026-09-20, so work is landing. The release history runs at a different pace: v1.3.0 on 2025-07-09, v1.3.1 on 2025-08-14, and v1.3.2 on 2025-12-07, which is the version string in package.json too. That gap decides what you install. Pin a tag and you get a tree roughly ten months behind the branch tip; track main and you get changes nobody has versioned. The project's own background explains the shape of the code. Its author was the primary contributor to the earlier workout.lol project, which was sold and then abandoned once the new owner concluded that exercise video licensing costs were prohibitively expensive, after fifteen unanswered contact attempts over nine months. Video hosting was the hard part last time, and it still is: every CSV row carries its own full_video_url and thumbnail column, so the importer cannot fill them for you.
Email preview, webhook forwarding and lint sit next to the seed scripts
The scripts block reaches further than setup. `pnpm email` starts an email development server, which pairs with the emails/ directory at the top level. `pnpm stripe-webhooks` runs stripe listen and forwards events to localhost:3000/api/webhooks/stripe, the local counterpart of the webhook secret in the environment file. Tests run through vitest, once with `pnpm test` and continuously with `pnpm test:watch`, with vitest.config.ts in the tree; linting is eslint via `pnpm lint` plus a separate fix target. Seeding is split by purpose: subscription plans, advanced workout data and leaderboard rows each have their own script. Migrations come in three flavours, a development command, a production command that reads .env.production through env-cmd, and a Vercel build path that generates the client, deploys migrations and builds in sequence. None of these are explained in prose, so the script names and their arguments are the whole interface for a self host without a deployment platform.
Editorial conclusion
Pick workout-cool if you want your workout plans and history inside a database you control, and if you are willing to finish the configuration yourself: the database name in the environment template and the one in the manual setup command differ, the billing, OAuth, analytics and mail keys are all empty, and exercise video URLs are yours to supply. Leave it alone if you wanted an installable phone app, a hosted service with a support desk, or a ready made exercise video library, since none of those ship in this repository. Before you depend on it, read the setup.sh your container runs, and decide whether you track main or the v1.3.2 tag from December 2025.
Frequently asked questions
What is workout cool?
A TypeScript fitness coaching platform you run yourself: a Next.js app on your own machine, with workout plans and progress stored in PostgreSQL 15 through Prisma and an exercise library loaded by a CSV importer. The repository is MIT licensed and its homepage is https://workout.cool.
how to use workout cool
Clone the repository and copy .env.example to .env, then pick a path. With Docker, make dev starts the database, runs migrations, seeds it and starts the dev server. Without Docker, run pnpm install, create the database, run npx prisma migrate dev and then pnpm dev. Either way the app answers on http://localhost:3000.
is workout cool free
The repository is MIT licensed and built for self hosting, so you run it on your own machine at no licence cost. Billing is marked optional in the environment template: the Stripe keys and the six regional price IDs for monthly and yearly plans are placeholders, left blank unless you want to charge for access.
is workout cool legit
It is a public repository under the Snouzy account, MIT licensed, not archived, with the default branch main last pushed on 2026-09-20. The most recent tagged release is v1.3.2 from 2025-12-07, and that same version number appears in package.json.
is workout cool safe
Self hosting keeps your plans and progress in a Postgres instance you run. The parts that reach outside are the ones you enable with your own credentials: Google OAuth, OpenPanel analytics, an SMTP server, and Stripe keys for billing. Every one of those values ships as a placeholder.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/snouzy-workout-cool)