Folgende Punkte müssen vor dem Betrieb mit mehreren Nutzern erledigt sein:
- Impressum-Platzhalter in
api/src/templates/imprint.htmlbefüllen (§ 5 TMG / Art. 10 ECRL) - Datenschutzerklärung unter
/privacyauf Vollständigkeit prüfen (DSGVO Art. 13/14) - DPIA (
docs/dpia.md) ausfüllen, unterzeichnen und archivieren (DSGVO Art. 35) -
PRIVACY_POLICY_VERSIONinenv/.env.apisetzen (für Consent-Versionierung) - Festplattenverschlüsselung auf dem Server aktivieren (TOM gem. DSGVO Art. 32)
- Backup-Verschlüsselung prüfen:
env/.env.backup→AGE_RECIPIENTgesetzt (age Public-Key) - E-Mail-Verifikation getestet (Resend API-Key in
env/.env.api)
- Docker + Docker Compose
make- A Garmin Connect account
- homelab-gateway running (optional, for homelab proxy setup)
Public SaaS (no homelab-gateway): Use
make up-public— bundles Caddy with automatic Let's Encrypt on a public domain. Full runbook: deployment-public.md.
git clone https://github.com/gerfru/PulseBase.git
cd PulseBase
cp env/.env.example env/.env
cp env/.env.app.example env/.env.app
cp env/.env.api.example env/.env.api
cp env/.env.sync.example env/.env.sync
cp env/.env.ml.example env/.env.mlEdit env/.env (DB-Admin, nur für Flyway/DB-Service):
DB_USER=garmin
DB_PASSWORD=<strong password>
HOST_IP=your-domain.comEdit env/.env.app (Shared App-Credentials für api + sync-service + ml-service):
DB_APP_USER=garmin_app
DB_APP_PASSWORD=<strong password>
DB_SYNC_USER=pulse_sync
DB_SYNC_PASSWORD=<strong password> # from make gen-secrets
DB_ML_USER=pulse_ml
DB_ML_PASSWORD=<strong password> # from make gen-secrets
FERNET_KEY=<from make gen-secrets>Each app service uses its own least-privilege DB role (V24): api →
DB_APP_*, sync-service →DB_SYNC_*, ml-service →DB_ML_*. All six values live inenv/.env.appbecause they feed both the Flyway role-creation placeholders and the service containers.
Edit env/.env.sync:
SYNC_INTERVAL_HOURS=2 # poll Garmin every X hours (default: 2)
SYNC_LOOKBACK_DAYS=30
SYNC_DAILY_DAYS=2Edit env/.env.ml:
ML_INFER_HOUR=7 # hour (UTC) for daily ML inference; training runs Sunday 3:00make gen-secretsCopy the output values:
SESSION_SECRET→env/.env.api(min. 32 characters, required)FERNET_KEY→env/.env.app(used by all 3 app services for token encryption)DB_APP_PASSWORD,DB_SYNC_PASSWORD,DB_ML_PASSWORD→env/.env.app(per-service DB role passwords, V24)
Optional — backups: make gen-secrets also prints an age-keygen hint. To enable the
encrypted backup container, generate an age keypair on a trusted offsite machine
(age-keygen -o pulsebase-backup.key), put the age1… public key as AGE_RECIPIENT in
env/.env.backup (cp env/.env.backup.example env/.env.backup), and keep the private key
offsite. See deployment-public.md.
make upThis builds the images, runs Flyway migrations, and starts all containers.
The API container joins the external proxy network shared with homelab-gateway's Caddy.
Wait until the API is ready:
make status
make logs-dashboardPublic SaaS (without homelab-gateway): Use
make up-publicinstead. This bundles Caddy with automatic Let's Encrypt — openhttps://<your-public-domain>. Full runbook: deployment-public.md.
Open https://your-domain.com/register in a browser.
Caddy (via homelab-gateway in home mode, or the bundled Caddy in make up-public) provides the valid TLS certificate via ACME/Let's Encrypt. See docs/deployment-public.md for the public setup.
Create your account with name, email, and password (min. 12 characters). Check all three consent checkboxes (required):
- Health data processing (DSGVO Art. 9)
- Terms of Service
- Age confirmation ≥ 16 years (DSGVO Art. 8)
After registering you are redirected to /login?verify=sent (or /login?verify=failed if
the email service is not configured).
If RESEND_API_KEY is set: Check your inbox and click the verification link.
If RESEND_API_KEY is not set (local/homelab): The token is printed to the API log.
Copy the token from make logs-dashboard and open:
https://your-domain.com/auth/verify/<token>
If the link expires (24h TTL) or never arrived, use /auth/resend-verify to request a new one.
Go to https://your-domain.com/garmin/link (or click the link on the dashboard).
Enter your Garmin Connect email and password. The password is used once to fetch a session token and then deleted from memory — it is never stored anywhere.
After linking Garmin, a sync starts automatically within 1 minute (the link sets
sync_requested = true, the sync-service polls for it every minute). Watch progress:
make logs-syncAfter sync completes, go to https://your-domain.com/dashboard.
The sync-service polls Garmin every SYNC_INTERVAL_HOURS (default: 2 hours).
No manual action needed after initial setup.
| Command | What it does |
|---|---|
make up |
Build images and start all services (requires homelab-gateway proxy network) |
make up-public |
Build and start a public instance with bundled Caddy + Let's Encrypt |
make down |
Stop all services |
make reset |
Stop + wipe all data + re-run migrations (deletes all users!) |
make migrate |
Run pending Flyway migrations |
make trigger-sync |
Request immediate Garmin sync for all users (no rebuild, processed within 1 min) |
make sync |
Rebuild sync-service + restart (triggers full backfill sync) |
make dashboard |
Rebuild and restart the API/dashboard container |
make analytics |
Rebuild and restart the ML analytics service |
make logs-dashboard |
Live logs from the API |
make logs-sync |
Live logs from the sync-service |
make logs-analytics |
Live logs from the ML analytics service |
make status |
Show container status |
make db |
Open a psql shell on the database |
make gen-secrets |
Generate SESSION_SECRET, FERNET_KEY, DB passwords + age-keygen hint |
make secure-env |
Set chmod 600 on all env files |
make backup |
Run an encrypted DB backup now (one-off; otherwise daily via the backup container) |
make restore-test |
Decrypt + TimescaleDB-correct restore of the latest backup into a throwaway DB (key path from AGE_IDENTITY in env/.env.backup) |
- Open
https://your-domain.com/registerin a browser (or incognito window) - Register the new account
- Verify the email (see Step 5 above)
- Log in and go to
/garmin/link - Link the second Garmin account
The sync-service picks up all users with garmin_linked = true from the database
automatically on the next run.
API won't start / migrations fail:
Check that the db container is healthy before the API starts.
make status
make logs-dashboardproxy network not found:
homelab-gateway must be running before make up. Start it first:
cd ../homelab-gateway && make upGarmin link fails:
- Check credentials (try logging into connect.garmin.com in a browser)
- Garmin sometimes requires 2FA — if so, the initial link may time out
Dashboard shows no data after sync:
make logs-sync # look for errors
make db # then: SELECT count(*) FROM activities WHERE user_id = 1;Account locked after failed logins: After 5 failed attempts the account is locked for 15 minutes (auto-unlock). To unlock immediately:
make dbThen in psql:
UPDATE users SET failed_login_attempts = 0, locked_until = NULL WHERE email = 'your@email.com';Reset everything (nuclear option):
make resetThis wipes the database and all tokens. All users must re-register and re-link.