LF Hub is a small server that follows one person's glucose data (Nightscout, Dexcom Share or both) and drives LoopFollow on the family's iPhones: the badge, the Live Activity, the alarms and Speak BG, while LoopFollow itself is closed. One hub serves one family and one person.
LF Hub is alpha software. It is not a medical device and not medical advice. Alarms depend on the hub, the network and Apple's push service, and any of them can fail. Keep LoopFollow's own alarms in mind as the fallback, and talk to your care team about how you rely on alerts.
The image ghcr.io/loopandlearn/lfhub:dev is built from the dev branch; released
versions will be tagged latest.
To build the image yourself, with the .NET 10 SDK and Docker installed:
git clone -b dev https://github.com/loopandlearn/lfhub && cd lfhub
dotnet publish lfhub.csproj -c Release -r linux-x64 /t:PublishContainer -p:ContainerRepository=lfhub -p:ContainerImageTags=devUse -r linux-arm64 for an arm64 machine such as a Raspberry Pi. This puts
lfhub:dev in the local Docker; use image: lfhub:dev below. A fork of the repository builds and pushes
ghcr.io/<you>/lfhub with its own GitHub Actions in the same way.
- A machine that is always on and can run containers: a small cloud server, a home server or a Raspberry Pi (the image is built for arm64 and amd64).
- PostgreSQL 14 or later. It can run next to the hub.
- A domain name and HTTPS in front of the hub (a reverse proxy such as Caddy, Traefik or nginx). The hub speaks plain HTTP on port 8080 and must only be reached through HTTPS.
- LoopFollow on iOS 26 or later on each phone, with an APN key under Settings → APN on the phone that sets up the hub. Family members on the same LoopFollow build (the same Apple team) need no key of their own.
Create three random values and keep them in a password manager. The hub cannot recover them.
openssl rand -base64 32 # LFHUB_MASTER_KEY: encrypts the stored passwords and keys
openssl rand -base64 24 # LFHUB_SETUP_TOKEN: makes a phone an owner (admin) of the hub
openssl rand -base64 24 # LFHUB_STATUS_KEY: optional, for a monitoring service- LFHUB_MASTER_KEY must be exactly 32 bytes, base64-encoded, as the command makes it. If it is lost or changed, the stored Nightscout token, Dexcom login and APN keys can no longer be read: the phones send their APN keys again on their own, and an owner must send the data source again.
- LFHUB_SETUP_TOKEN is the hub's master password. Use it only to set up your own phones. Family members join with an invite from the app instead.
services:
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: lfhub
POSTGRES_PASSWORD: change-me
POSTGRES_DB: lfhub
volumes:
- db:/var/lib/postgresql/data
lfhub:
image: ghcr.io/loopandlearn/lfhub:dev
restart: unless-stopped
depends_on: [db]
environment:
DatabaseConnectionstring: Host=db;Port=5432;Username=lfhub;Password=change-me;Database=lfhub
LFHUB_MASTER_KEY: ${LFHUB_MASTER_KEY}
LFHUB_SETUP_TOKEN: ${LFHUB_SETUP_TOKEN}
LFHUB_STATUS_KEY: ${LFHUB_STATUS_KEY}
ports:
- "127.0.0.1:8080:8080"
volumes:
db:Put the three secrets in a .env file next to docker-compose.yml (and keep that
file private), then run docker compose up -d. Point your reverse proxy's HTTPS
site at 127.0.0.1:8080. With Caddy, the whole site is:
hub.example.com {
reverse_proxy 127.0.0.1:8080
}
Run exactly one hub per database. A second instance only stands by (the hub
holds a database lock), so use one replica and the Recreate strategy so an
upgrade never runs two side by side.
apiVersion: v1
kind: Secret
metadata:
name: lfhub
type: Opaque
stringData:
DatabaseConnectionstring: Host=postgres;Port=5432;Username=lfhub;Password=change-me;Database=lfhub
LFHUB_MASTER_KEY: <from step 1>
LFHUB_SETUP_TOKEN: <from step 1>
LFHUB_STATUS_KEY: <from step 1>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: lfhub
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels: { app: lfhub }
template:
metadata:
labels: { app: lfhub }
spec:
containers:
- name: lfhub
image: ghcr.io/loopandlearn/lfhub:dev
ports:
- containerPort: 8080
envFrom:
- secretRef: { name: lfhub }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
livenessProbe:
httpGet: { path: /livez, port: 8080 }
periodSeconds: 30
failureThreshold: 4
---
apiVersion: v1
kind: Service
metadata:
name: lfhub
spec:
selector: { app: lfhub }
ports:
- port: 8080Expose the service through your ingress with TLS.
Check that it answers: https://hub.example.com/healthz should say ok.
- In LoopFollow, make sure Nightscout and/or Dexcom work and that an APN key is set under Settings → APN.
- Open Settings → LoopFollow Hub → Set Up as Owner. Enter the hub's address
(
https://hub.example.com), the setup token and the name of the person you follow. - The phone sends its data source, alarms and settings to the hub. Set Settings → Background Refresh to LoopFollow Hub.
- Work through the Ready list on the LoopFollow Hub page until every row is green, then send a test alarm with the phone locked.
On an owner's phone, open LoopFollow Hub → Invite Family Member. It shows a QR code that works once, within 24 hours. On the new phone, either choose "Join a Family Hub" when LoopFollow starts for the first time, or open Settings → LoopFollow Hub → Join with Invite, and scan the code.
The new phone can copy the alarms of an existing phone, start fresh, or take the place of an old phone of a member. It receives the data source from the hub, so it can still alarm by itself if the hub stops answering.
An owner can make another phone an owner, change a phone back to a member, and remove phones. A member's data source and APN settings are managed by the hub.
Add a monitor (for example UptimeRobot) for
https://hub.example.com/status?key=<LFHUB_STATUS_KEY>&fresh=1
It answers 200 while the hub, its database and the data source are healthy, and
503 otherwise, including when the latest reading is 12 minutes old or more.
/status without the key tells only whether the hub is up.
Back up the database regularly, for example with
pg_dump -Fc -U lfhub lfhub > lfhub.dump, and keep the master key in a
separate safe place: the stored passwords and keys in a backup can only be read
with it.
Pull the new image and restart the hub. It updates the database on start, by adding tables and columns, so the version before can run on it again if an upgrade has to be rolled back. A hub older than the phones' LoopFollow, or the other way around, shows on the LoopFollow Hub page, and the phone keeps alarming by itself until both understand each other.