Files
Stride/docs/DEPLOYMENT.md
T

3.6 KiB

Dokku deployment

App: stride; database: stride-db; domain: stride.peterstockings.com. The Dockerfile installs locked dependencies on Python 3.12. Dokku runs database migrations in the predeploy step, then one web container and one sync worker.

Mount /var/lib/dokku/data/storage/stride:/storage with ownership 1000:1000 and mode 0700. Set APP_ENV=production, INSTANCE_PATH=/storage/instance, DATA_ROOT=/storage/data, SECRET_KEY (random secret), TOKEN_ENCRYPTION_KEY (Fernet key). Linking PostgreSQL supplies DATABASE_URL. Preserve the encryption key separately from database backups; losing it requires Garmin reconnection.

Push to Gitea, then deploy with dokku git:sync --build stride https://gitea.peterstockings.com/peterstockings/Stride.git main on the server. Map the web port with dokku ports:set stride http:80:8000, then enable HTTPS using dokku letsencrypt:enable stride.

Sync

Upload a garminconnect 0.3.17 session JSON in Settings & imports. Credentials are validated with Garmin, encrypted, and assigned to the authenticated owner. Manual sync queues durable work; the worker polls every 30 seconds. Automatic sync runs hourly and checks the latest 100 activities, reusing download manifests and unique activity source keys to avoid duplicates. Older activities can be imported with FIT uploads or the operator's local import command.

Shared storage file locks prevent overlapping jobs and disconnect races. A crash releases the lock; due or pending work is retried after restart. Refresh tokens are persisted before downloading and after download failures. Transient failures back off up to six hours; rejected authentication pauses scheduling until the user reconnects and enables it again. Disconnect removes credentials and keeps previously imported activities. Only garmin.com is supported by the web connector.

Inspect dokku logs stride -p worker, the status in Settings, and /health. Run dokku run stride flask --app app sync-worker --once for an operator check. Do not run the local sync simultaneously using a copy of the same Garmin session.

Back up PostgreSQL, /storage, and configuration securely. Rate limiting still uses per-process memory; email verification and password recovery are not yet implemented. For a larger public deployment configure shared rate-limit storage.

Validation

Local automated suite: 39 passing tests, including manual/scheduled jobs, encryption, retry/authentication failure, locking, CSRF, and access control. Live verification on 2026-10-01:

  • Built and deployed on Dokku 0.38.23; all PostgreSQL migrations succeeded.
  • HTTPS certificate issued; external /health returned HTTP 200 and status: ok.
  • Migrated the existing local account and 18 activities, preserving its password hash.
  • Reused the local Garmin session on the Linux server, saved it encrypted, and imported one new activity (19 total).
  • Submitted a manual sync to the public HTTPS endpoint with an authenticated session, CSRF token, and same-origin Referer. The background worker completed it.
  • Moved next_sync into the past with pending=false and scheduling enabled. The running worker picked it up automatically and completed the scheduled run. This exercises the schedule due check without waiting an hour.
  • Both repeat syncs imported zero duplicates and reported zero failures.
  • Authenticated dashboard returned 200; anonymous private activity access returned 404.
  • Production uses one web container (two Gunicorn processes) and one worker; hourly scheduling remains enabled. The local session copy should no longer be used concurrently with the server.