Skip to content
Jakub Raczek edited this page Sep 11, 2026 · 1 revision

Docker

StatsServiceBook is available as a pre-built Docker image on Docker Hub at jraczek/statsservicebook. The image bundles all three data-source pipelines (Strava API, Strava scrape mode, HealthSync/Google Drive) under crond + lighttpd on Alpine Linux. Mount your config file, start the container, and the pages are live on port 80.

Prerequisites

  • Docker ≥ 20 or Podman ≥ 4
  • One filled-in config file (see Configuration below)
  • Credentials for your chosen data source (Strava API / scrape / HealthSync)

Quick start

1. Get a config template

Copy the template for your data source from the docker/ directory:

# Strava API or scrape mode:
cp docker/strava-my-activities.conf.example my-activities.conf

# HealthSync / Google Drive:
cp docker/healthsync-activities.conf.example healthsync.conf

# Club leaderboard (optional, in addition to my-activities):
cp docker/strava-leaderboard.conf.example leaderboard.conf

2. Fill in credentials

Edit the file you copied. The minimum required fields are:

Source Required fields
Strava API STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET, STRAVA_REFRESH_TOKEN
Strava scrape STRAVA_MY_SOURCE=scrape, STRAVA_SESSION_COOKIE
HealthSync GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN, DRIVE_FOLDER_ID

All path variables (STRAVA_MY_STATE_DIR, HEALTHSYNC_STATE_DIR, etc.) are pre-set to /data/... in the Docker templates — do not change them unless you know what you are doing.

3. Start with docker compose (recommended)

# Edit docker-compose.yml — uncomment the config volume line that matches your
# data source, then:
docker compose up -d

# Watch startup logs:
docker compose logs -f

Open http://localhost/strava/me/ once the first run completes.

3b. Or start with docker run

docker run -d --name statsservicebook \
  -p 80:80 \
  -v "$(pwd)/my-activities.conf:/etc/strava-my-activities.conf:ro" \
  -v statsservicebook_data:/data \
  -e TZ=Europe/Warsaw \
  -e RUN_ON_START=1 \
  jraczek/statsservicebook:latest

Configuration

Config files

Each data source is enabled by mounting its config file into the container. Only the files you mount are activated — unmounted sources are silently ignored.

Mount path Template Purpose
/etc/strava-my-activities.conf docker/strava-my-activities.conf.example My Activities (Strava API or scrape)
/etc/strava-leaderboard.conf docker/strava-leaderboard.conf.example Club leaderboard
/etc/healthsync-activities.conf docker/healthsync-activities.conf.example HealthSync / Google Drive

You can mount more than one config file — all enabled sources run in cron and on RUN_ON_START.

Environment variables

Variable Default Description
TZ (UTC) Timezone for cron scheduling and log timestamps
RUN_ON_START 0 Set to 1 to run all enabled scripts once at container start
CRON_LEADERBOARD 50 23 * * * Cron schedule for strava-leaderboard
CRON_MY_ACTIVITIES 55 23 * * * Cron schedule for strava-my-activities
CRON_HEALTHSYNC 55 23 * * * Cron schedule for healthsync-activities

Persistent data volume

The container writes all state (OAuth token cache, activity NDJSON store, per-activity detail JSON, bike-service data) to /data. Mount this as a named volume or host directory so data survives container restarts:

volumes:
  - statsservicebook_data:/data   # named volume (docker compose)
  # or:
  - /opt/statsservicebook:/data   # host directory

The web output (/www/strava/) is regenerated on every cron run from the state in /data — it does not need to be persisted separately.


docker-compose.yml reference

The repo includes a ready-to-use docker-compose.yml. Uncomment the volume lines for the data sources you use:

services:
  statsservicebook:
    image: jraczek/statsservicebook:latest
    ports:
      - "80:80"
    environment:
      TZ: Europe/Warsaw
      RUN_ON_START: "1"
    volumes:
      - statsservicebook_data:/data
      - ./my-activities.conf:/etc/strava-my-activities.conf:ro
      # - ./leaderboard.conf:/etc/strava-leaderboard.conf:ro
      # - ./healthsync.conf:/etc/healthsync-activities.conf:ro
    restart: unless-stopped

volumes:
  statsservicebook_data:

Running scripts on demand

# Run a single script now (inside the running container):
docker exec statsservicebook strava-my-activities
docker exec statsservicebook healthsync-activities
docker exec statsservicebook strava-leaderboard

Viewing logs

# Container startup + first-run output:
docker logs statsservicebook

# Cron job output (written by entrypoint to /var/log/ inside container):
docker exec statsservicebook tail -f /var/log/strava-my-activities.log
docker exec statsservicebook tail -f /var/log/healthsync-activities.log
docker exec statsservicebook tail -f /var/log/strava-leaderboard.log

Image tags

Tag Meaning
latest Latest release from the main branch
v1.2.3 Specific release tag
1.2 Latest patch for a minor version

Images are built for linux/amd64, linux/arm64, and linux/arm/v7 (Raspberry Pi), so the same tag works on all three architectures.


Building locally

docker build -t statsservicebook .
docker run -d --name statsservicebook \
  -p 80:80 \
  -v "$(pwd)/my-activities.conf:/etc/strava-my-activities.conf:ro" \
  -v statsservicebook_data:/data \
  -e TZ=Europe/Warsaw \
  -e RUN_ON_START=1 \
  statsservicebook

Publishing a new release (maintainers)

The GitHub Actions workflow .github/workflows/docker-publish.yml builds and pushes to Docker Hub automatically:

# Tag the release and push — the workflow handles the rest:
git tag v1.2.3
git push origin v1.2.3

Required repository secrets:

  • DOCKERHUB_USERNAME — Docker Hub username (ocaramba)
  • DOCKERHUB_TOKEN — Docker Hub access token (generate at hub.docker.com → Account settings → Security)

The workflow also supports manual dispatch from the GitHub Actions UI to push latest without tagging.


Differences from the OpenWrt install

Aspect OpenWrt router Docker
Web server uhttpd (built-in) lighttpd (in image)
Scheduler OpenWrt cron BusyBox crond (in image)
State storage /mnt/sda5/... (USB) /data volume
Port 80 (served by router) configurable (-p HOST:80)
Config SSH to /etc/*.conf bind-mount from host

The shell scripts themselves are identical — the image ships the same POSIX sh scripts that run on the router.

Clone this wiki locally