QuickFit is a self-hosted workout tracker: plan your training, log sessions from your phone at the gym, and share plans with other people. I built it because I was sick of the Google Sheets mess I'd made for myself.
- Exercise library with per-exercise history, so you can see how a lift has progressed over time
- Training plan builder (sessions, sets, reps, rest periods, ...)
- Workout logging, including in-progress sessions you can pick back up and finish later
- Share a plan with another user; they log their own progress against it
- Set a default plan per user, whether it's your own or one shared with you
- Google Health integration - connect your account via OAuth, pull workout data in and push completed QuickFit sessions back out
Built as much to learn as to ship: not just a tracker, but a "properly" built app end to end - real auth, a real deployment story, a real CI/CD pipeline.
| Kubernetes | Same Kustomize manifests deploy to k3s on my home server and minikube locally - no environment drift |
| Cloudflare Tunnel | The home server is exposed to the internet without opening a single port or renting a VPS |
| Docker + GitHub Actions | Every push builds, lints and tests; every tag pushes images to ghcr.io and rolls out to prod |
| FastAPI | Async Python API with dependency injection, JWT auth and a resource-first layout |
| PostgreSQL + SQLAlchemy + Alembic | Async SQL stack with versioned migrations, run as a K8s Job on deploy |
| OpenAPI + Orval | Frontend API types and hooks are generated from the backend spec - the two can't drift |
| React + TypeScript + Tailwind | Boring-in-a-good-way frontend, responsive for phone (gym) and desktop (planning) |
| TanStack Query | Server state, caching and auth handling without the boilerplate |
| Taskfile | One entry point for every dev/lint/test/deploy command (task dev, task lint, task k8s-local) |
Kubernetes & CI/CD
- Two clusters, one set of manifests -
k3son my home server for prod,minikubelocally, both built from the samek8s/baseand diverging only through Kustomize overlays (ingress class, image tag, replica count, resources). - Self-hosted, publicly reachable - a Cloudflare Tunnel fronts the cluster's ingress, so the app is available on a real domain with TLS while the home network stays closed to inbound traffic.
- Postgres in-cluster - a
StatefulSet+ PVC (local-path) instead of a managed database, specifically to get hands-on with StatefulSets, PVCs, and a headless service — the kind of thing a managed DB would have hidden from me. - CI/CD-driven rollout - GitHub Actions builds and pushes images on every push, then on a tag push: applies k8s secrets from GitHub Secrets, runs Alembic migrations as a one-off
Job, patches the image tag withkustomize edit set image, and waits on the rollout. - Secrets - plain k8s
Secrets created from GitHub Secrets in the pipeline (or from a local untracked.envforminikube) — simplest option that's still safe enough at this scale.
Backend
- Project layout - resource-first (
auth/,plan/,exercise/, ... each owning its own router/schema/service) following zhanymkanov/fastapi-best-practices, rather than the classic layeredrouters/,schemas/,services/split. - Auth - JWT access + refresh tokens in httpOnly cookies (stateless access token, stateful/revocable refresh token) plus Google OAuth 2.0 login.
- OpenAPI - the spec is generated straight from the SQLAlchemy models and Pydantic schemas, no hand-written spec to keep in sync.
- Testing - Docker Compose spins up a real Postgres for integration tests instead of mocking the database.
- Tooling -
structlogfor structured logs, Ruff for linting and formatting,uvas the package manager.
Frontend
- Generated API layer - Orval turns the backend's OpenAPI spec into typed TanStack Query hooks; no hand-written fetch calls or API types anywhere.
- Responsive by default - one codebase for mobile (logging workouts at the gym) and desktop (building plans on a bigger screen) — see the screenshots above.
- UI - designed and implemented with the help of Claude Code (I'm no designer at all).
quickfit/
├── api/ # FastAPI backend, resource-first structure (auth/, plan/, exercise/, ...)
│ ├── src/
│ ├── alembic/ # DB migrations
│ └── tests/
├── frontend/ # React + TypeScript + Vite + Tailwind
│ └── src/
├── k8s/
│ ├── base/ # shared manifests (deployments, services, ingress, postgres)
│ └── overlays/ # local (minikube) and prod (k3s) patches
├── context/ # planning docs (auth, k8s, product context)
└── Taskfile.yml # dev/lint/test/deploy commands
MIT — see LICENSE.