An open API service indexing awesome lists of open source software.

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.

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


Home tab
Active workout
History
Workout detail
Progress
Settings

---

## 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)