Workout diary with GitHub-style year visualization. Log daily sets, track body weight, and visualize training history with intensity heatmaps.
| Workout tab | Trends tab |
|---|---|
![]() |
![]() |
- Architecture
- Quick start
- Configuration
- API server options
- Frontend options
- Local network only
- Thanks
Workout Diary runs as two independent services:
┌──────────────────────────┐ ┌──────────────────────────┐
│ workoutdiary-frontend │─HTTP──▶│ workoutdiary-api │
│ Web UI (default :8080) │ │ JSON API (default :8851│
└──────────────────────────┘ └───────────┬──────────────┘
│
SQLite DB
| Service | Image | Description |
|---|---|---|
| API backend | ghcr.io/rwlove/workoutdiary-api |
Owns the SQLite database, exposes a JSON REST API |
| Web frontend | ghcr.io/rwlove/workoutdiary-frontend |
Serves the browser UI, talks to the API over HTTP |
docker compose upOr run each service manually:
# Start the API backend (stores data in /data/WorkoutDiary)
docker run --name exdiary-api \
-v ~/.dockerdata/WorkoutDiary:/data/WorkoutDiary \
-p 8851:8851 \
ghcr.io/rwlove/workoutdiary-api
# Start the web frontend
docker run --name exdiary-frontend \
-e API_URL=http://<YOUR_HOST_IP>:8851 \
-p 8080:8080 \
ghcr.io/rwlove/workoutdiary-frontendThen open http://localhost:8080 in your browser.
Both services are configured exclusively via environment variables. No config file is required.
| Variable | Description | Default |
|---|---|---|
PORT |
Listen port | 8851 |
HOST |
Listen address | 0.0.0.0 |
DATA_DIR |
SQLite data directory (also settable via -d flag) |
/data/WorkoutDiary |
POSTGRES_DSN |
PostgreSQL connection string — when set, PostgreSQL is used instead of SQLite | "" (SQLite) |
API_KEY |
Require this value on every X-Api-Key request header; empty = no auth |
"" |
THEME |
Any Bootswatch theme (lowercase) or extras: emerald, grass, grayscale, ocean, sand, wood |
grass |
COLOR |
Background: light or dark |
dark |
HEATCOLOR |
Heatmap cell color | #03a70c |
PAGESTEP |
Rows per page | 10 |
TZ |
Timezone | "" |
Set POSTGRES_DSN to switch the backend from SQLite to PostgreSQL:
POSTGRES_DSN=postgres://user:password@host:5432/workoutdiary
The schema is versioned and managed automatically on startup — no manual CREATE TABLE needed. When switching from SQLite, use the Migrate SQLite → PostgreSQL button on the Settings page to copy existing data across without data loss.
| Variable | Description | Default |
|---|---|---|
PORT |
Listen port | 8080 |
API_URL |
Base URL of the API server | http://localhost:8851 |
API_KEY |
X-Api-Key value sent to the API (must match API server API_KEY) |
"" |
NODE_PATH |
URL of a node-bootstrap instance for offline use | "" |
TZ |
Timezone | "" |
By default the app loads themes, icons, and fonts from the internet. For an air-gapped setup, run the node-bootstrap sidecar and set NODE_PATH on the frontend:
docker run --name node-bootstrap \
-v ~/.dockerdata/icons:/app/icons \
-p 8850:8850 \
aceberg/node-bootstrap
docker run --name exdiary-frontend \
-e API_URL=http://<YOUR_HOST_IP>:8851 \
-e NODE_PATH=http://<YOUR_HOST_IP>:8850 \
-p 8080:8080 \
ghcr.io/rwlove/workoutdiary-frontend- Exercise library — organize exercises into groups, store default weight/reps/intensity
- Daily workout log — autosaves on every change (no Save button)
- Body weight tracking — log weight and view a rolling chart
- Heatmap history — GitHub-style workout intensity and per-exercise color heatmaps
- Stats page — per-exercise intensity charts with period filtering
- Dark mode by default — full Bootstrap dark theme
- PWA support — installable as a home screen app
- All Go packages listed in go.mod
- Bootstrap and Bootswatch themes
- Chart.js and chartjs-chart-matrix
- Gin
- Favicon and logo: Flaticon

