From 8b321b4182c5bb4a709cf356ae186f5c161754d0 Mon Sep 17 00:00:00 2001 From: Quentin Caron Date: Sat, 17 Oct 2026 19:30:39 -0700 Subject: [PATCH] feat: add Open Health Sync compose example --- README.md | 1 + examples/open-health-sync/README.md | 33 ++++++++++ examples/open-health-sync/docker-compose.yml | 63 ++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 examples/open-health-sync/README.md create mode 100644 examples/open-health-sync/docker-compose.yml diff --git a/README.md b/README.md index 737a19a2f..3a96bc6d3 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,7 @@ Software that does not fit in another section. - [Network-Multitool](examples/network-multitool) - Multi-arch multitool for container network troubleshooting. - [IT-Tools](examples/it-tools) - Collection of handy online tools for developers, with great UX. - [Scratch-Map](examples/scratch-map) - An open-source scratch-off style map to track your travels. +- [Open-Health-Sync](examples/open-health-sync) - Self-hosted health data aggregator for Apple Health, Health Connect, Oura, WHOOP, Withings and more, with a REST API, FHIR R4 and a read-only MCP server. ## 🌟 Star History [![Star History Chart](https://star-history.dera.page/svg?repos=Haxxnet/Compose-Examples&type=Date)](https://star-history.dera.page/#Haxxnet/Compose-Examples&type=Date) diff --git a/examples/open-health-sync/README.md b/examples/open-health-sync/README.md new file mode 100644 index 000000000..a03a7d0cf --- /dev/null +++ b/examples/open-health-sync/README.md @@ -0,0 +1,33 @@ +# References + +- https://github.com/soma-intelligence/open-health-sync +- https://github.com/soma-intelligence/open-health-sync/blob/main/docs/self-hosting.md + +# Notes + +Open Health Sync collects health data from Apple Health, Health Connect, Oura, WHOOP, Withings, Fitbit, Dexcom and other sources into Postgres (TimescaleDB). It serves it through a REST API, FHIR R4, Open mHealth, CSV/Parquet exports and a read-only MCP server. + +Before the first start: + +```` +# set the same database password in both places (POSTGRES_PASSWORD and OHS_DATABASE_URL) +# generate the encryption key and paste it into OHS_ENCRYPTION_KEY +openssl rand -base64 32 + +# the server runs as 1000:1000, so its raw volume must be owned by that user +mkdir -p /mnt/docker-volumes/open-health-sync/raw +chown -R 1000:1000 /mnt/docker-volumes/open-health-sync/raw +```` + +Keep the encryption key safe: it seals stored provider tokens, and losing it means reconnecting every provider. + +After `docker compose up -d`, check the configuration and create an API key (printed once): + +```` +docker exec open-health-sync /ohs doctor +docker exec open-health-sync /ohs admin tenant create --name dev +```` + +No device or provider keys yet? `docker exec open-health-sync /ohs seed --demo` loads 90 days of synthetic data and prints calls to try. + +Set `OHS_PUBLIC_URL` to the address users reach (for example behind Traefik), since provider OAuth callbacks use it. Provider keys and every other setting are documented in the project's `.env.example`. diff --git a/examples/open-health-sync/docker-compose.yml b/examples/open-health-sync/docker-compose.yml new file mode 100644 index 000000000..49d6f98c3 --- /dev/null +++ b/examples/open-health-sync/docker-compose.yml @@ -0,0 +1,63 @@ +services: + + open-health-sync-db: + image: timescale/timescaledb:2.22.1-pg17 + container_name: open-health-sync-db + hostname: open-health-sync-db + restart: unless-stopped + expose: + - 5432 + volumes: + - ${DOCKER_VOLUME_STORAGE:-/mnt/docker-volumes}/open-health-sync/postgres:/var/lib/postgresql/data + environment: + - POSTGRES_USER=ohs + - POSTGRES_PASSWORD=CHANGEME # adjust + - POSTGRES_DB=ohs + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U ohs -d ohs"] + interval: 5s + timeout: 3s + retries: 20 + start_period: 10s + + open-health-sync: + image: ghcr.io/soma-intelligence/open-health-sync:0.1.0 + container_name: open-health-sync + hostname: open-health-sync + user: 1000:1000 # owner of the raw volume below + command: ["up"] # runs database migrations, then the server + restart: unless-stopped + depends_on: + open-health-sync-db: + condition: service_healthy + stop_grace_period: 35s + ports: + - 8080:8080/tcp + expose: + - 8080 + volumes: + - ${DOCKER_VOLUME_STORAGE:-/mnt/docker-volumes}/open-health-sync/raw:/var/lib/ohs/raw + environment: + - OHS_DATABASE_URL=postgres://ohs:CHANGEME@open-health-sync-db:5432/ohs?sslmode=disable&pool_max_conns=20 # same password as above + - OHS_ENCRYPTION_KEY=CHANGEME # openssl rand -base64 32; keep it safe, it seals stored provider tokens + - OHS_PUBLIC_URL=http://localhost:8080 # public base URL, used for OAuth callbacks + - OHS_RAW_STORE=file:///var/lib/ohs/raw + healthcheck: + test: ["CMD", "/ohs", "doctor", "--quick"] + interval: 10s + timeout: 12s + retries: 6 + start_period: 15s + #networks: + # - proxy + #labels: + # - traefik.enable=true + # - traefik.docker.network=proxy + # - traefik.http.routers.open-health-sync.rule=Host(`health.example.com`) + # - traefik.http.services.open-health-sync.loadbalancer.server.port=8080 + # # Optional part for selecting certificate resolver + # - traefik.http.routers.open-health-sync.tls.certresolver=myresolver-http + +#networks: +# proxy: +# external: true