Self-hosted training analytics app with PostgreSQL, file import, and a React dashboard.
PWRX is intended to run as a standalone app from this repository.
- End users only need this repo, Docker, and PostgreSQL.
- No separate
data-hubrepository or multi-app platform is required. - Shared-host setups are optional operator variants, not the product baseline.
- Docker + Docker Compose
- PostgreSQL
- Optional, private-only: additional connector access by separate arrangement
German version: README.de.md
- Copy
.env.exampleto.env
cp .env.example .env- Fill required variables in
.env
POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD- Optional:
DATA_HUB_DATA_DIR(default:./data) - Optional:
WATCH_FOLDER_SMB_PATH(UI hint for self-hosted watch-folder users; default install shows./data/imports/watch) Note: The Postgres password is set only on first initialization of the DB volume. If you change it later, you must either update the DB user password inside Postgres or reset the volume.
- Start services
docker compose up -d- Open services
Dashboard: http://localhost:8088
API health: http://localhost:3001/api/health
pgAdmin: http://localhost:5050
This is the official public baseline for this repository.
- file import works without Strava API access
- no private adapter package access is required
- no SSH deploy key is required
Set in .env:
ADAPTER_FILE_ENABLED=true
ADAPTER_STRAVA_ENABLED=falseEverything else in .env.example related to Strava/private adapters is optional and only relevant for selected private operators.
Then restart backend + dashboard:
docker compose up -d --force-recreate strava-tracker strava-dashboardThe public repository does not officially ship or support provider API connector setup for normal end users.
Reason:
- Strava API access is subject to Strava's developer review and athlete-capacity restrictions.
- New apps start in a single-athlete mode until reviewed by Strava.
- Because of that, PWRX public docs must not present Strava API enablement as a standard public feature.
Official Strava sources:
If you explicitly enable the private connector path:
ADAPTER_STRAVA_ENABLED=trueyou are entering a private maintainer/operator setup that requires:
- private adapter access
- Strava credentials
- a host SSH directory containing
pwrx_adapter_deploy
In this private mode, the Docker runtime injects the private adapter package during container startup. The public package.json intentionally does not depend on it by default.
Recommended private settings:
ADAPTER_STRAVA_PACKAGE=git+ssh://git@github.com/cyclenoid/pwrx-adapter-strava.git
ADAPTER_STRAVA_MODULE=@cyclenoid/pwrx-adapter-stravaImportant:
ADAPTER_STRAVA_PACKAGEis the install source used by Docker/npmADAPTER_STRAVA_MODULEis the runtime module id used by Node- keep those separate; a Git URL is not a valid
require()module id
If that key is missing, backend startup will fail with:
Missing /root/.ssh/pwrx_adapter_deploy for private adapter install
Important:
PWRX_SSH_DIRmust be a host path, not the container path/root/.ssh- example Windows host path:
C:/Users/<you>/.ssh - example Linux host path:
/home/<you>/.sshThis private connector path is maintainer-only and not part of the official public support contract.
Important technical rule:
- public-core no longer falls back to local Strava modules
- if the private adapter cannot be installed or loaded, Strava stays disabled
- this is intentional
On first start, PWRX runs an initial import/sync initialization. In private connector operator setups, an API-backed initial sync can take time depending on data size and rate limits.
- Auto sync runs daily at the configured time.
- Optional: catch-up after startup if the machine was offline.
- Manual sync is available in the UI (Settings/Dashboard).
API endpoints:
- Full sync (activity + backfill):
POST /api/sync(alias:POST /api/sync/full) - Backfill only (gaps):
POST /api/sync/backfill
If the device is off during the scheduled time, enable "Catch-up after startup" in Settings. The next start will run the missed sync.
git pull
docker compose up -dIf a release adds DB columns/tables, you must run migrations after updating:
docker compose exec strava-tracker npm run db:migrateLocal dev:
cd apps/strava
npm run db:migrateOptional auto-migrate on startup:
- Set
MIGRATE_ON_START=1in.env.
Check status:
docker compose exec strava-tracker npm run db:checkExports, logs, and photos are stored in DATA_HUB_DATA_DIR (default: ./data).
- Quickstart:
docs/IMPORT_QUICKSTART.md - Provider guide (Zwift/Wahoo/Garmin/Apple Health):
docs/IMPORT_PROVIDER_GUIDE.md - Docker release test runbook:
docs/DOCKER_RELEASE_TEST_PLAN.md - Deployment runbook (public repo -> Unraid + Strava override):
docs/DEPLOYMENT_RUNBOOK.md - PowerShell smoke script:
scripts/docker-release-smoke.ps1
- PWRX watches the container path
/imports/watch. - Standard Docker install exposes the corresponding host path
./data/imports/watchand shows it in the UI as copy target. - Optional: set
WATCH_FOLDER_SMB_PATHin.envto show a network share path in the UI (for example\\\\unraid\\pwrx-import).
Public backend checks now run without the private adapter.
Optional private-adapter access validation in CI can still use repository secret:
PWRX_ADAPTER_DEPLOY_KEY
Secret value:
- full private SSH key (OpenSSH format) that matches a read-only deploy key on
cyclenoid/pwrx-adapter-strava. - keep OpenSSH key formatting intact (multi-line):
-----BEGIN OPENSSH PRIVATE KEY------ base64 lines
-----END OPENSSH PRIVATE KEY-----
Without this secret, public backend lint/build/tests still run. Only the optional private-adapter access check is skipped.
For local Docker tests with the private adapter on Windows/Linux:
- set
PWRX_SSH_DIRin.env(for exampleC:/Users/<you>/.sshon Windows) - ensure
pwrx_adapter_deployexists in that directory and validates:
ssh-keygen -y -f ~/.ssh/pwrx_adapter_deploy- Security policy and vulnerability reporting:
SECURITY.md
What do the photo sync and download numbers mean?
Photo sync = metadata from the connected source (for example URLs/captions). Downloads = local files saved to disk. Both are per-run counts.
Why is the first sync slow?
Large histories and provider rate limits can slow down the initial import. It will continue in the background.
Why are segments still pending?
Segments are filled in chunks during backfill. If you hit rate limits, run manual sync again later.
Can I run without auto sync?
Yes. Disable Auto Sync in Settings and use the manual sync button when needed.
Laptop or machine not always on?
Enable catch-up after startup. It will run once the machine is back online.
Do I need migrations after updating?
Only when a release adds DB schema changes. Then run npm run db:migrate.
Apache-2.0 (see LICENSE).
Buy me a coffee: https://buymeacoffee.com/cyclenoid