diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index c9d376c..80021ec 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -11,6 +11,8 @@ permissions:
jobs:
publish:
runs-on: ubuntu-latest
+ outputs:
+ digest: ${{ steps.push.outputs.digest }}
permissions:
contents: read
packages: write
@@ -52,3 +54,23 @@ jobs:
subject-name: ghcr.io/${{ github.repository_owner }}/openrun
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
+
+ # The public demo on Fly.io (docs/DEMO.md) follows main. Each deploy also
+ # resets it, since the demo seeds itself on start.
+ deploy-demo:
+ if: github.ref == 'refs/heads/main'
+ needs: publish
+ runs-on: ubuntu-latest
+ concurrency:
+ group: deploy-demo
+ cancel-in-progress: true
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: superfly/flyctl-actions/setup-flyctl@ed8efb33836e8b2096c7fd3ba1c8afe303ebbff1 # master, 2026-04-08 (Node 24)
+ - run: >-
+ flyctl deploy -c deploy/demo/fly.toml --ha=false
+ --image ghcr.io/${{ github.repository_owner }}/openrun@${{ needs.publish.outputs.digest }}
+ env:
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
diff --git a/README.md b/README.md
index 1535f3c..bc38ed1 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,8 @@ An open-source, self-hosted running app: log your runs, sync from your watch, an
Your runs are yours: they're private unless you choose to share them, and clubs only ever see what you send them. Run it for yourself, or for friends and family who trust you with their data. It's inspired by [openGym](https://github.com/DuarteSantos8/openGym).
+**Try it:** [openrun-demo.fly.dev](https://openrun-demo.fly.dev) signs you in as Sam, a runner in a run club. It resets every night.
+
> **Status: early development.** Accounts, the run log, stats, privacy controls, run clubs and watch sync work. v0.1 is out ([release notes](https://github.com/stretch0/openRun/releases/tag/v0.1.0)). See [ROADMAP.md](ROADMAP.md).
diff --git a/deploy/demo/fly.toml b/deploy/demo/fly.toml
index b6415ef..0058b14 100644
--- a/deploy/demo/fly.toml
+++ b/deploy/demo/fly.toml
@@ -24,7 +24,7 @@ primary_region = 'lhr'
[http_service]
internal_port = 3000
force_https = true
- # Always on: seeding takes 15 seconds or more, too long to make a visitor
+ # Always on: seeding takes about a minute here, too long to make a visitor
# wait for a cold start, and stopping would also reset the demo mid-visit.
auto_stop_machines = 'off'
auto_start_machines = true
diff --git a/docs/DEMO.md b/docs/DEMO.md
index cf4a52c..8cea989 100644
--- a/docs/DEMO.md
+++ b/docs/DEMO.md
@@ -3,7 +3,7 @@
`DEMO_MODE=true` turns an instance into a public demo. On every start, and every night at 04:00 UTC, it:
1. Empties the database and the `files/` and `clubs/` folders under `DATA_DIR`.
-2. Seeds sample data through the API (`apps/api/src/demo/seed.ts`): Sam, a runner with 26 weeks of runs around Regent's Park, two pairs of shoes and a privacy zone, plus a run club with six other members, weekly runs, RSVPs and coffee orders. The dates follow the day it runs, so the demo always looks current. Seeding takes about 15 seconds (longer on a small machine); the server answers once it's done.
+2. Seeds sample data through the API (`apps/api/src/demo/seed.ts`): Sam, a runner with 26 weeks of runs around Regent's Park, two pairs of shoes and a privacy zone, plus a run club with six other members, weekly runs, RSVPs and coffee orders. The dates follow the day it runs, so the demo always looks current. Seeding takes about 15 seconds on a laptop and about a minute on Fly's smallest shared CPU; the server answers once it's done.
Visitors sign in as Sam with **Try the demo** on the sign-in page. Everyone shares that login, so demo mode turns off anything that would change the login, let a visitor keep a foothold, or host their files:
@@ -27,10 +27,16 @@ fly deploy -c deploy/demo/fly.toml --ha=false
Change `app` and `ORIGIN` in the file if you use another app name or your own domain.
+After that, the Docker image workflow deploys each new image from `main` to the demo, which also resets it. It needs a deploy token for the app in the repository's `FLY_API_TOKEN` secret:
+
+```bash
+fly tokens create deploy -a openrun-demo | gh secret set FLY_API_TOKEN -R stretch0/openRun
+```
+
Keep it to **one machine, always on**:
- Each machine has its own database, so two machines would show visitors different demos and lose their sign-in as requests move between them.
-- A stopped machine would seed again when it wakes, which takes about 15 seconds and resets the demo in the middle of someone's visit.
+- A stopped machine would seed again when it wakes, which takes about a minute and resets the demo in the middle of someone's visit.
## README screenshots