← Writing

There Are a Million Fitness Apps. I Built My Own Anyway.

  • #ai
  • #agentic-engineering
  • #self-hosting
  • #personal-software
  • #side-projects
  • #dotnet

There’s no shortage of apps for tracking a workout or a weigh-in. Dozens of good ones, most of them cheap, some of them free. So the honest question isn’t “why not pay for one” — it’s why I built my own instead of just downloading one.

The answer isn’t the money. It’s that building a custom app got cheap enough that “exactly what I want” stopped being a luxury.

What I needed, and what got built

The need was ordinary. A weigh-in most mornings, with a trend I can trust instead of the daily noise. Workouts that follow a structure — blocks, sets, timed holds, exercises done per side — with a timer that runs the session so I don’t have to think mid-set. A weekly plan that tells me what today is, and a record of whether I did it. That’s it.

Every app I tried did two of those the way I wanted and the third its own way. And in all of them, the data was theirs.

So: Soma Tracker. A small web app that lives on my phone’s home screen. Weight logging with a trend line. A guided player that walks through blocks and timers, pauses, and resumes after you close the app. A weekly plan. Goals with live progress. History, by day and by month.

Soma Tracker — monthly training calendar, each day colored by session type
The month at a glance. One dot per session type; ❌ is a day I deliberately didn't train, which is different from no data.
Soma Tracker — a workout template with blocks, exercises and timed holds
A workout: blocks A/B/C, per-side exercises split into left and right, timed holds. Tap ▶ and the player runs it.
Soma Tracker — the weekly training plan
The week. Today is highlighted; each day knows its type and, if there is one, the workout to launch.

It was usable the same evening I started it — logging a weigh-in, running a workout. Everything since has been added the same way: I hit something while training, I describe it, it ships. It isn’t finished. It’s in daily use while it grows.

The stack

Personal project, my own time, my own choices — so here it is with nothing anonymized.

LayerWhatNotes
BackendASP.NET Core minimal API on .NET 10 — one Program.csOne endpoint per resource. No controllers, no DI ceremony I don’t need.
DatabaseSQLite in WAL mode, raw SQL via Microsoft.Data.SqliteNo ORM. The schema is applied idempotently on startup. Safe to read from outside while the app is running.
FrontendVanilla-JS PWA: index.html, app.js, style.css, a service workerNo framework, no build step. Installs to the home screen, works offline.
HostingA systemd service on my own VPS, behind CaddyReachable only over my tailnet. No public exposure, so no auth layer to build — the network is the perimeter.
DevelopmentClaude Code, from the terminal and from my phoneI describe the feature, the agent writes it against the codebase, I test it on the phone.

A few notes on why it looks like this:

What custom actually buys you

Not the features. Any decent app has features. What you get is the two things a product can’t sell you.

Features on demand, on my schedule. A feature idea mid-workout is a sentence to Claude Code that evening. No backlog vote, no waiting for a product team to agree with me. The per-side exercises, the resumable session, the “I didn’t train today, on purpose” marker — each exists because I wanted it that week, and most of them landed the same day I asked for it.

My assistant reads and writes it directly. Aura is the app I built to talk to Claude Code sessions from my phone through a chat, with characterized assistants on top — each with its own persona and skills. One of them is Soma, the wellness one. Because the tracker is an API on localhost plus a SQLite file on the same machine, Soma doesn’t need an integration. I tell it “ran 5k this morning” in the chat and it posts the session to the API. I ask how the month is going and it queries the database. It has an evening check-in that spots gaps in the log and asks. And when I report a weight, it renders the trend chart from the app and drops it into the conversation.

None of that is a plugin, a webhook, or a product feature. It’s what you get for free when the data is a file on your own machine and the API is a port on localhost.

The data outlives the app. Months of sessions and weigh-ins in one SQLite file I can copy, query, or hand to the next tool. When I later built the app I use to run my own content pipeline, it borrowed this exact shape — minimal API, SQLite, vanilla PWA — because it had already proven itself here. Owning the format, not just the data, is what makes a set of small tools composable instead of a pile of apps that don’t talk to each other.

What I gave up

No app-store polish. No community, no social feed, no one else’s UX researcher having already solved the problem for me. If something’s clunky, I only have myself to blame — and myself to fix it. It’s a single-user tool on a private network; it will never scale past me, and it doesn’t need to.

In exchange: it fits the way I actually train, not the way a product team assumed I would. Nothing to cancel. And the database is mine, in a format anything I build next can read straight away — including whatever I build after this one.