Dreeve (Statistics for Strava)
Dreeve, formerly Statistics for Strava, is a self-hosted fitness dashboard for FIT, TCX, and GPX files or activities imported through the Strava API. The chart deploys the official image with a web container and the mandatory background daemon sharing one SQLite-backed persistent volume.
Dreeve v5.6.2 includes the upstream fix for the October 2026 critical PHP remote-code-execution vulnerability.
Earlier versions should be upgraded. The former robiningelbrecht/strava-statistics:v5.0.0 tag now serves only a
rename notice and is no longer the application image.
Key Features
- Official
docker.io/robiningelbrecht/dreeve:v5.6.2image - Web and daemon containers co-located for safe RWO SQLite access
- Shared persistent database, file, build-cache, gear-maintenance, and watch paths
- File-import and Strava API modes
- Chart-managed, existing, or External Secrets for runtime and OAuth credentials
- Non-root UID/GID 82, read-only root filesystems, RuntimeDefault seccomp, and disabled token automounting
- Optional Ingress, Gateway API, NetworkPolicy, PodDisruptionBudget, and dual-stack Service controls
- Probes based on the upstream FrankenPHP metrics endpoint and migrated database
Architecture
User -> Ingress or HTTPRoute -> Service -> Dreeve web container
|
shared RWO PVC (SQLite/data)
|
Dreeve daemon
The Deployment is intentionally one replica with Recreate. SQLite is single-writer, and the mandatory daemon must
share the same filesystem and node attachment as the web process.
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install dreeve helmforge/strava-statistics -f values.yaml
OCI registry:
helm install dreeve oci://ghcr.io/helmforgedev/helm/strava-statistics -f values.yaml
Prepare the Admin Password
ADMIN_PASSWORD_HASH must contain a bcrypt hash, not a plaintext password. Generate it with the same image version:
docker run --rm -it --entrypoint bin/console robiningelbrecht/dreeve:v5.6.2 security:hash-password
Helm values preserve $ characters as written; do not apply Docker Compose’s double-dollar escaping.
When dreeve.appSecret is empty, the chart generates a 64-character value and reuses the value stored in the live
Secret during upgrades. Set dreeve.appSecret or dreeve.existingSecret explicitly for reproducible GitOps and offline
rendering.
Deployment Examples
dreeve:
appUrl: https://dreeve.example.com
appSecret: replace-with-a-long-random-value
adminUsername: admin
adminPasswordHash: '$2y$13$replace-with-the-generated-bcrypt-hash'
importMode: stravaApi
strava:
clientId: '12345'
clientSecret: replace-me
refreshToken: replace-me
timezone: America/Sao_Paulo
persistence:
size: 10Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: dreeve.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: dreeve-tls
hosts:
- dreeve.example.comdreeve:
appUrl: https://dreeve.example.com
appSecret: replace-with-a-long-random-value
adminPasswordHash: '$2y$13$replace-with-the-generated-bcrypt-hash'
importMode: files
strava:
clientId: ''
clientSecret: ''
refreshToken: ''
persistence:
size: 10GiBoth containers mount the file watch directory at /var/www/watch.
dreeve:
appUrl: https://dreeve.example.com
existingSecret: dreeve-runtime
existingSecretAppSecretKey: app-secret
existingSecretAdminPasswordHashKey: admin-password-hash
existingSecretApiKeyKey: api-key
strava:
existingSecret: strava-credentials
existingSecretClientIdKey: client-id
existingSecretClientSecretKey: client-secret
existingSecretRefreshTokenKey: refresh-tokenThe api-key key is optional; without it the upload API remains closed.
externalSecrets:
enabled: true
items:
- name: runtime
storeRef:
name: platform-secrets
kind: ClusterSecretStore
targetName: dreeve-runtime
data:
- secretKey: app-secret
remoteRef:
key: dreeve/runtime
property: app-secret
- secretKey: admin-password-hash
remoteRef:
key: dreeve/runtime
property: admin-password-hash
- name: strava
storeRef:
name: platform-secrets
kind: ClusterSecretStore
targetName: strava-credentials
data:
- secretKey: client-id
remoteRef:
key: dreeve/strava
property: client-id
- secretKey: client-secret
remoteRef:
key: dreeve/strava
property: client-secret
- secretKey: refresh-token
remoteRef:
key: dreeve/strava
property: refresh-token
dreeve:
existingSecret: dreeve-runtime
strava:
existingSecret: strava-credentialsdreeve:
appUrl: https://dreeve.example.com
existingSecret: dreeve-runtime
strava:
existingSecret: strava-credentials
gatewayAPI:
enabled: true
httpRoutes:
- name: web
parentRefs:
- name: public-gateway
namespace: gateway-system
hostnames:
- dreeve.example.comConfiguration Reference
Image and Runtime
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/robiningelbrecht/dreeve |
Official Dreeve image. |
image.tag |
string | v5.6.2 |
Pinned release with the critical PHP fix. |
image.pullPolicy |
string | IfNotPresent |
Kubernetes image pull policy. |
dreeve.appUrl |
string | http://localhost:8080 |
Public root URL. |
dreeve.importMode |
string | stravaApi |
stravaApi or files. |
dreeve.appSecret |
string | "" |
Session-signing secret; generated and preserved when empty. |
dreeve.adminUsername |
string | admin |
Admin panel username. |
dreeve.adminPasswordHash |
string | "" |
Bcrypt admin password hash. |
dreeve.apiKey |
string | "" |
Optional activity-upload API key. |
dreeve.existingSecret |
string | "" |
Existing Secret for Dreeve runtime secrets. |
daemon.enabled |
boolean | true |
Run the mandatory Dreeve background daemon. |
The mock-* defaults allow deterministic chart rendering and runtime tests. They are not safe production credentials.
Set unique values or reference existing Secrets before exposing Dreeve.
Strava API
| Parameter | Type | Default | Description |
|---|---|---|---|
strava.clientId |
string | mock-client-id |
Validation-only OAuth client ID. |
strava.clientSecret |
string | mock-client-secret |
Validation-only OAuth client secret. |
strava.refreshToken |
string | mock-refresh-token |
Validation-only refresh token. |
strava.existingSecret |
string | "" |
Existing Secret containing OAuth credentials. |
strava.timezone |
string | UTC |
Runtime timezone. |
strava.config |
string | legacy YAML | One-time v4 migration input. |
strava.extraEnv |
array | [] |
Additional environment entries for both containers. |
Strava credentials are required only when dreeve.importMode=stravaApi.
Persistence
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.enabled |
boolean | true |
Create or mount persistent shared storage. |
persistence.size |
string | 2Gi |
PVC request. |
persistence.storageClass |
string | "" |
StorageClass; empty uses the cluster default. |
persistence.accessModes |
array | [ReadWriteOnce] |
PVC access modes. |
persistence.existingClaim |
string | "" |
Existing PVC name. |
persistence.bootstrap.enabled |
boolean | true |
Prepare Dreeve directories and legacy database links. |
The shared volume stores the SQLite database, application files, build cache, gear-maintenance assets, and watched imports. Snapshot or back up the PVC before every upgrade.
Resources and Security
| Parameter | Type | Default | Description |
|---|---|---|---|
resources.requests.cpu |
string | 100m |
Web CPU request. |
resources.requests.memory |
string | 256Mi |
Web memory request. |
resources.limits.cpu |
string | 2 |
Web CPU limit. |
resources.limits.memory |
string | 5Gi |
Web memory limit. |
daemon.resources.requests.cpu |
string | 100m |
Daemon CPU request. |
daemon.resources.requests.memory |
string | 256Mi |
Daemon memory request. |
daemon.resources.limits.cpu |
string | 2 |
Daemon CPU limit. |
daemon.resources.limits.memory |
string | 5Gi |
Daemon memory limit. |
serviceAccount.automountServiceAccountToken |
boolean | false |
Mount a Kubernetes API token. |
networkPolicy.enabled |
boolean | false |
Render supplied ingress and egress rules. |
podDisruptionBudget.enabled |
boolean | false |
Protect the single pod from voluntary disruption. |
The web container adds only NET_BIND_SERVICE, required by the upstream FrankenPHP binary capability. The daemon drops
all capabilities. Both run as UID/GID 82 with read-only root filesystems and bounded writable volumes.
Exposure
| Parameter | Type | Default | Description |
|---|---|---|---|
service.port |
integer | 80 |
Service port targeting named port http. |
service.ipFamilyPolicy |
string | omitted | Optional single/dual-stack policy. |
service.ipFamilies |
array | omitted | Ordered IP families. |
ingress.enabled |
boolean | false |
Render Ingress. |
gatewayAPI.enabled |
boolean | false |
Render HTTPRoutes. |
externalSecrets.enabled |
boolean | false |
Render ExternalSecrets. |
NetworkPolicy
When enabled, empty rule lists deny that traffic direction. Declare DNS plus every Strava, map, notification, or AI destination used by the installation:
networkPolicy:
enabled: true
ingress:
- ports:
- protocol: TCP
port: 8080
egress:
- {}
Replace unrestricted egress with cluster-specific destinations where the CNI supports them.
Upgrading from v4
Dreeve v5 moves YAML settings into SQLite and adds the mandatory daemon. Keep strava.config during the first successful
v5 startup so supported settings can migrate, then verify them in the admin panel. Images and gear purchase prices are
not migrated upstream. Back up the PVC and read the
official v4-to-v5 guide.
Troubleshooting
Daemon waits for schema
The web container owns database migrations; the daemon waits for them. Inspect both logs:
kubectl logs deploy/<release>-strava-statistics -c strava-statistics
kubectl logs deploy/<release>-strava-statistics -c daemon
Pod reports permission errors
The PVC must be writable by UID/GID 82. Confirm that the CSI driver honors fsGroup and repair ownership out of band for
existing volumes rather than disabling non-root execution.
Import is OOMKilled
Large histories can consume substantial memory. Increase the web and daemon memory limits together. The chart configures
PHP for 4G and defaults each container limit to 5Gi.
Admin login fails
Verify the stored value is a bcrypt hash and that dreeve.appUrl exactly matches the browser’s public root URL. Helm
values use literal $ characters; Compose escaping is not required.