Garmin Grafana¶
Pulls fitness data from Garmin Connect for one or more users and stores it in a local InfluxDB instance for visualization in Grafana. Runs in the home namespace.
- Source —
ghcr.io/arpanghosh8453/garmin-fetch-data - Database — InfluxDB 1.11 (
local-path,worker-1a) - Namespace —
home - Flux manifests —
flux/apps/noah/home/garmin-grafana/
Architecture¶
graph LR
subgraph garmin["Garmin Connect (cloud)"]
GC[Garmin Connect API]
end
subgraph home["Namespace: home"]
FHD[garmin-fetch-hd\nFenix 7S]
FXKH[garmin-fetch-xkh]
IDB[InfluxDB 1.11\nport 8086]
end
subgraph nas["Synology NAS"]
BCK[(NFS backup\npvc-garmin-influxdb-backup)]
end
subgraph grafana["Grafana"]
DASH[Dashboard]
end
GC -->|pull via garmin-fetch-data| FHD
GC -->|pull via garmin-fetch-data| FXKH
FHD -->|write GarminStats_hd| IDB
FXKH -->|write GarminStats_xkh| IDB
IDB -->|local-path PVC| worker-1a[(worker-1a)]
worker-1a -->|hourly rsync| BCK
IDB -->|InfluxDB datasource| DASH
Components¶
garmin-fetch-data¶
One deployment per user. Each pod runs garmin-fetch-data, which authenticates to Garmin Connect, downloads activity and health data (steps, sleep, heart rate, workout FIT files), and writes it to InfluxDB.
| Setting | hd | xkh |
|---|---|---|
| Deployment | garmin-fetch-hd |
garmin-fetch-xkh |
| Device | Fenix 7S | — |
| InfluxDB database | GarminStats_hd |
GarminStats_xkh |
| Secret | garmin-hd-secret |
garmin-xkh-secret |
| Token path | garmin-tokens PVC · hd/ subPath |
garmin-tokens PVC · xkh/ subPath |
ALWAYS_PROCESS_FIT_FILES=True is set so workout files are re-processed on restart, preventing data gaps after pod restarts.
Password encoding
The ExternalSecret template base64-encodes the Garmin password before injecting it, because the container requires GARMINCONNECT_BASE64_PASSWORD rather than a plaintext credential.
InfluxDB¶
InfluxDB 1.11 stores all time-series fitness data. HTTP auth is enabled; the garmin user has per-database write access.
| Setting | Value |
|---|---|
| Image | influxdb:1.11 |
| Port | 8086 |
| Index | tsi1 |
| Node | worker-1a (pinned via nodeSelector) |
| Storage | garmin-influxdb-data PVC — 10Gi local-path |
local-path is used instead of NFS because InfluxDB's WAL and file locking are incompatible with NFS — corruption can occur. The node is pinned to worker-1a so the local PVC and the backup CronJob always land on the same node.
Adding a new user database
The INFLUXDB_DB env var only creates the initial database (GarminStats_hd). Additional databases must be created manually:
Storage¶
| PVC | Class | Size | Purpose |
|---|---|---|---|
garmin-influxdb-data |
local-path |
10Gi | Live InfluxDB data on worker-1a |
garmin-influxdb-backup |
syno-nfs-retain |
10Gi | Hourly rsync target on Synology NAS |
garmin-tokens |
syno-nfs-retain (RWX) |
10Mi | Shared OAuth token store; one subPath per user |
The token PVC is ReadWriteMany so multiple fetch pods can mount it simultaneously, each reading their own subdirectory.
Backup¶
A CronJob runs hourly on worker-1a, rsyncing the live InfluxDB data to the NFS backup PVC:
Both the CronJob and the InfluxDB deployment are pinned to worker-1a via nodeSelector — this is required because local-path PVCs are node-local and both jobs must access the same volume.
Secrets¶
| Secret | Vault path | Keys |
|---|---|---|
garmin-influxdb-secret |
garmin/influxdb |
admin-password, user-password |
garmin-hd-secret |
garmin/hd |
email, base64-password (auto-encoded by ESO template) |
See Security for ExternalSecrets details.
Adding a new user¶
- Add Garmin credentials to Vault at
garmin/<username> - Add an
ExternalSecretforgarmin-<username>-secret(see commented template inexternalsecret.yaml) - Copy
deployment-hd.yaml→deployment-<username>.yaml, updating the deployment name, label,subPath,INFLUXDB_DATABASE, and secret reference - Create the InfluxDB database manually (see tip above)
- Add the new deployment to
kustomization.yaml