Nathan 54a885120d feat(sparkyfitness): add sparkyfitness stack to heimdall
- Compose file with pinned images (v0.16.6.1), healthchecks, Traefik routing
- .env.example with all required variables and generation commands
- README covering access, appdata, backup scope, upgrade procedure
- KBA-002: deployment reference with verification and rollback steps
- repo-deploy.prompt.md: reusable end-to-end deployment workflow prompt
- Session snapshot 2026-05-10

Source: https://github.com/CodeWithCJ/SparkyFitness
2026-05-10 21:58:38 -04:00

182 lines
7.1 KiB
Markdown

# SparkyFitness
**Node:** heimdall
**Stack:** `sparkyfitness`
**URL:** https://fitness.castaldifamily.com
**Version:** v0.16.6.1
**Source:** https://github.com/CodeWithCJ/SparkyFitness
**Docs:** https://codewithcj.github.io/SparkyFitness/
---
## Overview
SparkyFitness is a self-hosted, privacy-first fitness and health tracking platform — a full alternative to MyFitnessPal. It stores all data on infrastructure you control with no reliance on third-party services.
**Core capabilities:**
- Nutrition, exercise, hydration, sleep, fasting, mood, and body measurement tracking
- Goal setting and daily check-ins
- Interactive charts and long-term reports
- Multiple user profiles and family access
- Native OIDC, TOTP, Passkey, and MFA support
- Health platform integrations: Apple Health, Google Health Connect, Fitbit, Withings, Garmin, Polar, Strava
- Food database integrations: OpenFoodFacts, USDA, FatSecret, Nutritionix
- Recipe app integrations: Mealie, Tandoor
- Optional AI chatbot for conversational food/exercise logging (beta)
**Stack architecture:**
| Container | Image | Role |
|---|---|---|
| `sparkyfitness-db` | `postgres:18.3-alpine` | PostgreSQL database |
| `sparkyfitness-server` | `codewithcj/sparkyfitness_server:v0.16.6.1` | Node.js backend API |
| `sparkyfitness-frontend` | `codewithcj/sparkyfitness:v0.16.6.1` | React frontend (Nginx) |
---
## Node
Runs on **heimdall** — the general-purpose applications node. No GPU is required. Workload is typical of a personal web application.
---
## Access
| Method | Detail |
|---|---|
| **URL** | https://fitness.castaldifamily.com |
| **Auth** | SparkyFitness native auth (TOTP, passkeys, MFA supported) |
| **SSO** | Not configured — OIDC with Authentik can be added via env vars (see `.env.example`) |
| **Admin panel** | Set `SPARKY_FITNESS_ADMIN_EMAIL` in `.env` to auto-grant admin on first start |
> ⚠️ Set `SPARKY_FITNESS_DISABLE_SIGNUP=true` in `.env` after creating your initial account to prevent open registration.
---
## Appdata
All persistent data lives under `/mnt/appdata/sparkyfitness/data/` on the NFS share.
| Path | Contents | Backup Priority |
|---|---|---|
| `postgresql/` | All user data — nutrition logs, workouts, health metrics, accounts | 🔴 Critical |
| `uploads/` | Profile pictures and exercise images | 🟡 Important |
| `backup/` | App-generated PostgreSQL exports | 🟢 Nice-to-have |
Pre-create these directories before first deploy:
```bash
mkdir -p /mnt/appdata/sparkyfitness/data/postgresql
mkdir -p /mnt/appdata/sparkyfitness/data/backup
mkdir -p /mnt/appdata/sparkyfitness/data/uploads
```
---
## Environment Variables
Managed via `.env` (git-crypt encrypted). See `.env.example` for the full reference.
### Required Secrets
| Variable | Purpose | How to Generate |
|---|---|---|
| `SPARKY_FITNESS_DB_PASSWORD` | PostgreSQL superuser password | `openssl rand -base64 24` |
| `SPARKY_FITNESS_APP_DB_PASSWORD` | App-role DB password (limited privileges) | `openssl rand -base64 24` |
| `SPARKY_FITNESS_API_ENCRYPTION_KEY` | 64-char hex key for external integration encryption | `openssl rand -hex 32` |
| `BETTER_AUTH_SECRET` | Signs sessions and encrypts TOTP/2FA data | `openssl rand -hex 32` |
> ⚠️ `SPARKY_FITNESS_API_ENCRYPTION_KEY` — changing this after data is stored invalidates all encrypted external integration credentials.
> ⚠️ `BETTER_AUTH_SECRET` — changing this after users have enabled TOTP/2FA locks them out. Do not rotate without disabling 2FA first.
### Non-Secret Configuration (hardcoded in compose.yaml)
| Variable | Value | Notes |
|---|---|---|
| `SPARKY_FITNESS_FRONTEND_URL` | `https://fitness.castaldifamily.com` | Used for CORS and session binding |
| `SPARKY_FITNESS_DB_HOST` | `sparkyfitness-db` | Internal Docker service name |
| `SPARKY_FITNESS_DB_NAME` | `sparkyfitness_db` | Database name |
| `SPARKY_FITNESS_DB_USER` | `sparky` | Superuser for migrations |
| `SPARKY_FITNESS_APP_DB_USER` | `sparky_app` | Runtime app user (limited privileges) |
| `SPARKY_FITNESS_LOG_LEVEL` | `ERROR` | Use `DEBUG` temporarily for troubleshooting |
| `NODE_ENV` | `production` | |
| `TZ` | `Etc/UTC` | Adjust if local time is needed for date handling |
| `SPARKY_FITNESS_FORCE_EMAIL_LOGIN` | `true` | Failsafe — prevents OIDC misconfiguration lockout |
---
## Networking
| Network | Type | Used By |
|---|---|---|
| `proxy-net` | External (Traefik) | `sparkyfitness-frontend` only |
| `sparkyfitness-network` | Internal bridge | All three containers |
No host ports are exposed. All external traffic enters via Traefik.
### Traefik Routing
| Field | Value |
|---|---|
| **Router** | `sparkyfitness` |
| **Rule** | `Host(\`fitness.castaldifamily.com\`)` |
| **Entrypoint** | `websecure` (HTTPS) |
| **TLS** | Cloudflare cert resolver |
| **Middleware** | `security-headers@file` |
| **Backend port** | `80` (Nginx inside `sparkyfitness-frontend`) |
The frontend Nginx container internally proxies API requests to `sparkyfitness-server:3010` over `sparkyfitness-network`. No separate Traefik route is needed for the API.
---
## Dependencies
| Dependency | Type | Notes |
|---|---|---|
| PostgreSQL 18.3-alpine | Bundled | Self-contained — no shared DB instance |
| Traefik | External (core stack) | Must be running on `proxy-net` |
| Cloudflare DNS | External | Required for TLS cert resolver |
No Redis, GPU, or kernel module dependencies.
---
## Backup & Recovery
### What to Back Up
The `postgresql/` directory is the **single source of truth** for all user data. This must be in the backup rotation before going live.
```
/mnt/appdata/sparkyfitness/data/postgresql/ ← Critical
/mnt/appdata/sparkyfitness/data/uploads/ ← Important
```
### Restore Steps
1. Stop the stack: `docker compose down`
2. Restore the PostgreSQL data directory to `/mnt/appdata/sparkyfitness/data/postgresql/`
3. Restore uploads to `/mnt/appdata/sparkyfitness/data/uploads/`
4. Redeploy: `docker compose up -d`
5. Verify the DB is healthy: `docker compose logs sparkyfitness-db`
### Upgrading
> ⚠️ Auto-updating containers is **not recommended** by the maintainer. Watchtower should be excluded from this stack.
1. Review release notes at https://github.com/CodeWithCJ/SparkyFitness/releases
2. Take a backup of `postgresql/`
3. Update image tags in `compose.yaml`
4. Commit and push — Komodo will redeploy via webhook
5. Check logs: `docker compose logs --tail=50`
---
## Known Issues / Notes
- **Garmin microservice** (`sparkyfitness-garmin`) is still in development upstream. It is commented out of the compose file intentionally. Do not enable until the maintainer marks it stable.
- **Auto-update warning**: The maintainer explicitly warns against automated container updates between releases. Ensure Watchtower is not targeting this stack.
- **Postgres upgrade notice**: The upstream release notes flag a forthcoming mandatory Postgres upgrade. Monitor release notes and follow the [official Postgres upgrade guide](https://codewithcj.github.io/SparkyFitness/install/postgres-upgrade) when the time comes.
- **AI chatbot** is in beta — expect potential bugs if enabled.
- **Family & Friends access** is in beta — expect potential bugs if enabled.