Skip to content

Gman0909/FitnessTrack

Repository files navigation

FitnessTrack

A free, open-source, self-hosted progressive overload tracker for strength training. Log every set, track weight and rep progression across all your exercises, and let the built-in algorithm decide when to increase load — no spreadsheets, no subscriptions, no cloud required.

Runs on a Raspberry Pi, a home server, or any machine with Node.js. Your data stays on your hardware.


Screenshot 2026-05-18 084721 FitnessTrack progression feedback — per-set targets advance automatically via double progression FitnessTrack workout plan calendar — week-by-week session grid with completion status

Features

  • Dynamic double progression — each set climbs toward its rep-range ceiling, then adds weight and resets — automatically, based purely on the reps you actually log. No subjective check-ins
  • Per-exercise rep ranges — every exercise has an editable target rep range that drives its progression; seeded by movement type
  • Workout plans — create named plans with configurable training days and exercises; run multiple plans (one active at a time)
  • Calendar view — see every workout in a grid across weeks; navigate to any past or future session
  • Per-session logging — log weight and reps per set; skip individual sets or entire sessions
  • Actual-vs-target feedback — a per-set ▲ / = / ▼ glyph shows whether you beat, met, or missed each target
  • Exercise history — per-exercise weight progression chart
  • Plan cycling — when you finish the last session of a plan, the app prompts you to clone it into a new cycle with optional seed weights from any past week
  • Multi-user — each user has their own account, plans, and data; no data is shared between users
  • Equipment filter — select your available equipment; the exercise picker only shows relevant exercises
  • Custom exercises — create exercises not in the built-in library
  • Weight units — switch between kg and lbs; conversions are display-only, data is always stored in kg
  • Dark theme — designed for gym lighting

FitnessTrack is a self-hosted alternative to apps like Hevy, Strong, and JEFIT for lifters who want full control over their data. No account required beyond your own server, no paywalled features, no ads.


Contents


How the algorithm works

FitnessTrack uses dynamic double progression. Every exercise has a target rep range — e.g. 8–12 — and each set is its own progression track. There are no subjective check-ins: the next session's targets are derived purely from the reps you actually logged.

Per-set rules

Comparing your actual logged reps against the target you were given:

Situation Next session
Hit the top of the rep range Add weight by one increment; reps reset to the bottom of the range
Hit the target, still below the ceiling +1 rep, same weight
Fell short of the target but stayed in range Hold — same weight and target, try again
Couldn't reach the bottom of the range Ease off — weight down one increment; reps reset to the bottom
Skipped Unchanged

Because each set advances independently, your first (freshest) set tends to climb fastest and earn a weight bump before the others — the "dynamic" in dynamic double progression. Later sets settle at their own level.

Weight increments are the exercise's configured step, capped at 10% of the working weight and rounded to the nearest 0.5 kg.

Rep ranges

Each exercise carries its own rep range, seeded by movement type and editable any time — tap the ✎ on an exercise card or plan row:

Movement type Typical range
Heavy barbell compounds (deadlift, squat) 4–8
Moderate compounds (rows, presses) 8–10
Isolation (curls, extensions) 8–12
Small isolation (lateral raises, kickbacks) 12–20
Bodyweight 12–20

Worked example (Incline Curl, range 8–12, +1 kg increment)

Session You log Next target
1 20 × 9, 20 × 9, 20 × 8 20 × 10, 20 × 10, 20 × 9
2 20 × 12, 20 × 11, 20 × 10 21 × 8, 20 × 12, 20 × 11

In session 2 the first set reached the ceiling (12) → it adds weight and resets to the floor (21 × 8), while sets 2 and 3 keep climbing reps at 20 kg. The sets are now on slightly different weights — each progressing on its own.

Feedback

While an exercise is in progress its card shows a progressive-overload hint — how this session's target compares to your last performance. Once logged, each set shows a ▲ / = / ▼ glyph for beat / met / missed versus its target.

Bodyweight exercises

Bodyweight exercises (pull-ups, dips, etc.) have no weight axis — reps simply climb toward the ceiling. Once every set reaches the ceiling, a set is added (up to six).


Tech stack

Layer Technology
Server Node.js 20+, Express 4
Database SQLite (via better-sqlite3)
Auth JWT in httpOnly cookies, bcrypt password hashing
Client React 18, Vite 5, react-router-dom 6
Charts Recharts

How serving works: The Express server on port 3001 serves both the API and the compiled React client — but only once the client has been built (npm run build). The built files live in client/dist/, which is not committed to the repository. The setup scripts for each platform run the build step automatically. In development (Option 4), the client is served by Vite on port 5173 instead, which proxies API calls to the Express server on 3001.


Installation

Option 1 — Docker (recommended for all platforms)

Docker is the easiest and most consistent deployment method. It works on Windows, Linux, and Raspberry Pi without any manual dependency management.

Prerequisites: Docker Desktop (Windows/Mac) or Docker Engine (Linux/Pi — see below).

git clone https://github.com/Gman0909/FitnessTrack.git
cd FitnessTrack
docker-compose up -d

The build runs automatically inside Docker (including the React client). Once complete, the app — frontend and API — is available at http://localhost:3001.

Important: Docker builds better-sqlite3 (a native addon) inside the container for the target architecture. Always build on the machine you intend to run on. Cross-compilation is not supported by the default setup.

To stop: docker-compose down
Data is stored in a Docker volume (fitness_data) and persists across container restarts and rebuilds.
To change the port: set PORT=8080 in a .env file in the project root before running docker-compose up.


Option 2 — Raspberry Pi (native, no Docker)

Tested on Raspberry Pi OS (Bookworm/Bullseye), Raspberry Pi 3 and 4.

1. Install Node.js

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node --version  # should print v20.x.x or higher

2. Clone and set up

git clone https://github.com/Gman0909/FitnessTrack.git
cd FitnessTrack
bash scripts/setup.sh

This installs dependencies, builds the React client into client/dist/, and seeds the exercise library. The build step is required — without it the server has no frontend to serve.

3. Run

npm start

The app — frontend and API — is available at http://localhost:3001, or from other devices on your network at http://<pi-ip-address>:3001.

4. Run as a system service (auto-start on boot)

The setup script handles this automatically — at the end of bash scripts/setup.sh it will ask if you want to install the service. It detects your username, working directory, and Node.js path automatically.

If you skipped that step or need to reinstall the service manually:

bash scripts/setup.sh   # re-run and answer yes to the service prompt

Or install manually (replace the values to match your system):

sudo tee /etc/systemd/system/fitnesstrack.service > /dev/null <<EOF
[Unit]
Description=FitnessTrack
After=network.target

[Service]
Type=simple
User=$(whoami)
WorkingDirectory=$(pwd)
ExecStart=$(which node) server/index.js
Restart=on-failure
RestartSec=5
Environment=PORT=3001
Environment=DATABASE_PATH=$(pwd)/fitness.db

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable fitnesstrack
sudo systemctl start fitnesstrack

Check status: sudo systemctl status fitnesstrack
View logs: sudo journalctl -u fitnesstrack -f

Install Docker on Raspberry Pi (if you prefer Option 1)

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

Then follow the Docker instructions above.


Option 3 — Windows (native, no Docker)

Prerequisites: Node.js 20 LTS (includes npm). During installation, check the box to install build tools (required for better-sqlite3).

1. Clone and set up

Open Command Prompt or PowerShell:

git clone https://github.com/Gman0909/FitnessTrack.git
cd FitnessTrack
scripts\setup.bat

This installs dependencies, builds the React client into client\dist\, and seeds the exercise library. The build step is required — without it the server has no frontend to serve.

2. Run

scripts\start.bat

Or directly:

npm start

The app — frontend and API — is available at http://localhost:3001.

Run as a Windows background service

Install NSSM (Non-Sucking Service Manager), then:

nssm install FitnessTrack "C:\Program Files\nodejs\node.exe" "server\index.js"
nssm set FitnessTrack AppDirectory "C:\path\to\FitnessTrack"
nssm set FitnessTrack AppEnvironmentExtra PORT=3001
nssm start FitnessTrack

Option 4 — Development setup

Use this if you want to modify the code. Vite's dev server provides hot-reload for the client.

Note: In development, the frontend runs on port 5173 (Vite), not 3001. Port 3001 is the API only. Vite proxies all /api requests to the Express server automatically.

git clone https://github.com/Gman0909/FitnessTrack.git
cd FitnessTrack
npm run install:all   # installs both server and client dependencies
npm run setup         # create database and seed exercises

Then open two terminals:

# Terminal 1 — API server (port 3001)
npm run dev

# Terminal 2 — React dev server with hot-reload (port 5173)
cd client && npm run dev

Open http://localhost:5173 in your browser.


Configuration

Copy .env.example to .env and edit as needed:

PORT=3001
DATABASE_PATH=./fitness.db
Variable Default Description
PORT 3001 Port the server listens on
DATABASE_PATH ./fitness.db Path to the SQLite database file

In Docker, DATABASE_PATH defaults to /data/fitness.db inside a persistent volume.


Remote access

To access FitnessTrack from your phone or outside your home network without opening router ports, use Cloudflare Tunnel:

# Install cloudflared on Pi or your server machine
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install -y cloudflared

# Start a temporary public tunnel (no account needed for quick testing)
cloudflared tunnel --url http://localhost:3001

This prints a temporary https://xxxx.trycloudflare.com URL you can open on any device. For a permanent URL, create a free Cloudflare account and set up a named tunnel.


HTTPS (self-hosted, LAN or public)

FitnessTrack runs plain HTTP by default. If you need HTTPS — for example, to allow browsers to use the camera or vibration API, or to access the app from outside your network securely — you have two options:

Option A — Caddy (simplest, auto-HTTPS)

Caddy handles TLS certificates automatically via Let's Encrypt.

# Install Caddy (Debian/Ubuntu/Pi)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy

Create /etc/caddy/Caddyfile:

your-domain.com {
    reverse_proxy localhost:3001
}

Then: sudo systemctl reload caddy

Caddy automatically obtains and renews the certificate. Replace your-domain.com with your actual domain (must be publicly reachable for Let's Encrypt).

Option B — nginx + Let's Encrypt (Raspberry Pi / Linux)

sudo apt install -y nginx certbot python3-certbot-nginx

# Create site config
sudo tee /etc/nginx/sites-available/fitnesstrack <<'EOF'
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_cache_bypass $http_upgrade;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/fitnesstrack /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# Obtain certificate (requires domain pointing to this machine)
sudo certbot --nginx -d your-domain.com

Certbot modifies the nginx config to add HTTPS and sets up automatic renewal.

LAN-only HTTPS (no domain required)

For LAN access only, use a self-signed certificate or mkcert. Note that browsers will show a warning for self-signed certs unless you import the CA into your devices.


First run

  1. Navigate to the app in a browser
  2. Click Register and create your account (name, avatar glyph, username, password)
  3. Go to Setup → select the equipment you have access to
  4. Go to Plans → create a new plan, choose your training days
  5. Click into the plan → Configure → add exercises to each day
  6. Click Activate on the plan
  7. Go to Workout — your first session is ready to log

Updating

In-app (Linux / Raspberry Pi with systemd service):

If FitnessTrack is running as a systemd service, you can update it without a terminal. Navigate to Setup in the app → scroll to the bottom → click Check for updates. The app pulls the latest code, rebuilds the client, and restarts itself automatically. The page will reload once the new version is live.

This requires the server to be running under systemd with Restart=on-failure so the process manager brings it back up after the restart.

Linux / Raspberry Pi (terminal) — run the update script (pulls, rebuilds, and restarts the systemd service if active):

bash scripts/update.sh

Or with the npm alias: npm run update

Windows:

scripts\update.bat

Then restart the server manually (npm start, or nssm restart FitnessTrack if running as a service).

Manual steps (any platform):

git pull
npm run install:all
npm run build
# Restart the server

With Docker:

git pull
docker-compose up -d --build

The exercise library (server/seed.js) is seeded automatically every time the server starts. New exercises added in updates will appear in your library after a restart — no manual migration needed. Existing exercises and all user data are preserved.

Custom exercises you create in the app are stored only in your local fitness.db and are never overwritten by updates.


Data

All data is stored in a single SQLite file (fitness.db by default). To back it up, copy that file while the server is stopped. To restore, replace the file and restart.

# Backup
cp fitness.db fitness.db.backup

# With Docker (copy out of the volume)
docker run --rm -v fitnesstrack_fitness_data:/data -v $(pwd):/backup alpine \
  cp /data/fitness.db /backup/fitness.db.backup

Releases

No releases published

Packages

 
 
 

Contributors

Languages