https://github.com/bndct-devops/lifty
Self-hosted personal workout tracker. PWA, multi-profile, themes, rest timer, analytics.
https://github.com/bndct-devops/lifty
docker fastapi pwa react self-hosted workout-tracker
Last synced: 6 days ago
JSON representation
Self-hosted personal workout tracker. PWA, multi-profile, themes, rest timer, analytics.
- Host: GitHub
- URL: https://github.com/bndct-devops/lifty
- Owner: bndct-devops
- License: mit
- Created: 2026-02-28T13:39:20.000Z (4 months ago)
- Default Branch: dev
- Last Pushed: 2026-03-13T17:58:36.000Z (3 months ago)
- Last Synced: 2026-03-14T05:57:29.540Z (3 months ago)
- Topics: docker, fastapi, pwa, react, self-hosted, workout-tracker
- Language: JavaScript
- Size: 1.59 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# lifty
> A self-hosted, privacy-first workout tracker inspired by [Strong](https://www.strong.app). No accounts, no cloud, no subscriptions — just your data on your own server.
**FastAPI · React PWA · SQLite · Docker Compose**
---
## Screenshots
---
## Features
### Workouts
- Start a workout, add exercises on the fly, log sets with weight + reps
- Previous session's sets shown inline as reference
- Reorder exercises during a workout
- Rename workouts, add notes
- Mark rest days from the home screen
### Rest Timer
- Configurable duration (60 / 90 / 120 / 180s) saved per profile
- Web Audio ding + vibration on finish
- Background-accurate — stays correct after screen lock or tab switch
- Push notification fires even when the screen is off (Android + iOS PWA)
### Progress & History
- Full workout log with monthly calendar view
- Per-workout detail sheet — stats, muscle group breakdown, sets with estimated 1RM
- PRs per exercise (Epley 1RM), grouped by body part
- Weekly / daily volume bar chart (sets or tonnage)
- Muscle group donut chart
- 26-week activity heatmap
### Profiles & Settings
- Multiple profiles on a single instance
- Per-profile: unit (kg / lbs), theme, avatar colour, week start day, rest duration, ding toggle
- Themes: Dark, Light, AMOLED, Tokyo Night, Dracula, Nord, Gruvbox, Rosé Pine, [Catppuccin](https://catppuccin.com) Mocha / Macchiato / Frappé / Latte
### Import & Export
- Strong CSV import — bring in your full workout history
- CSV export per profile
### PWA
- Installable on iOS and Android
- Offline support via service worker
- Flamingo barbell icon, themed status bar
---
## PWA & Service Worker
The service worker provides two things:
| Feature | How it works |
|---|---|
| **Offline support** | Static assets cached on first load; API falls back to cached responses when offline |
| **Background notifications** | Rest timer end time posted to SW on start; SW fires `showNotification` at the right time regardless of whether the page is suspended |
**Platform support:**
| Platform | Offline | Background notification |
|---|---|---|
| Android | ✅ | ✅ |
| iOS 16.4+ (PWA) | ✅ | ✅ — add to home screen first |
| iOS < 16.4 / desktop | ✅ | ❌ — in-page ding still works when visible |
---
## Stack
| Layer | Tech |
|---|---|
| Backend | FastAPI + SQLModel + SQLite, Python 3.11 |
| Frontend | React 18 + Vite + plain CSS |
| Serving | nginx (frontend), uvicorn (backend) |
| Containers | Docker + Docker Compose |
| CI | GitHub Actions → `ghcr.io` (amd64 + arm64) |
---
## Running locally
```bash
./scripts/dev_up.sh
```
Builds both containers and seeds the exercise library.
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Backend API | http://localhost:8000 |
| API docs | http://localhost:8000/docs |
**Reset the database:**
```bash
rm data/lifty.db && docker compose restart backend
```
---
## Self-hosting
Images are built and pushed to `ghcr.io` on every push to `main` (amd64 + arm64).
Create a `docker-compose.yml` on your host:
```yaml
services:
backend:
image: ghcr.io/bndct-devops/lifty-backend:latest
restart: unless-stopped
expose:
- "8000"
volumes:
- /mnt/user/appdata/lifty:/data # adjust path as needed
environment:
- LIFTY_DB=/data/lifty.db
# - LIFTY_PASSWORD=your-password-here
frontend:
image: ghcr.io/bndct-devops/lifty-frontend:latest
restart: unless-stopped
ports:
- "3420:80"
depends_on:
- backend
```
Then:
```bash
docker compose pull
docker compose up -d
```
Update the volume path to wherever you want the SQLite database stored on your host.
---
## Security
By default the API has **no authentication** — fine for local/VPN use, but **set a password before exposing to the internet**.
### Enable instance auth
Uncomment `LIFTY_PASSWORD` in your `docker-compose.yml`:
```yaml
environment:
- LIFTY_DB=/data/lifty.db
- LIFTY_PASSWORD=your-strong-password
```
Restart the backend. The app will show a password screen on load. Once unlocked, a 30-day JWT is stored in the browser — you won't be prompted again until it expires.
- **Change/reset password**: Settings → Instance Auth → Change Password (or set `LIFTY_PASSWORD` env var again and restart to override)
- **Sign out**: Settings → Instance Auth → Sign Out
## Project structure
```
backend/
main.py # FastAPI app, all endpoints
models.py # SQLModel table definitions
schemas.py # Pydantic request/response types
db.py # engine + table creation
seed_exercises.py # built-in exercise library (runs on startup)
frontend/
public/
sw.js # service worker — offline cache + background notifications
manifest.json # PWA manifest
favicon.svg # flamingo barbell icon
src/
App.jsx # entire frontend
api.js # fetch wrappers for all backend endpoints
styles.css # CSS custom properties + layout
nginx.conf # proxies /api/* to backend
scripts/
dev_up.sh # one-command local dev start
docker-compose.yml # local dev (builds from source)
.github/workflows/
build-push.yml # CI: build multi-arch images, push to ghcr.io
```
---
## Acknowledgements
- Inspired by [Strong](https://www.strong.app) — the best commercial workout tracker, which lifty aims to self-host-replace
- Catppuccin theme palette by [Catppuccin](https://catppuccin.com)