Deploying the OpenSOP Public Demo to Fly.io
This runbook walks through standing up opensop-demo at demo.opensop.ai
using fly.demo.toml. It is a companion to docs/deploy-fly.md (primary
deployment). The demo app is intentionally minimal and cost-optimised.
Heads up: every
flyctlcommand that provisions infrastructure costs
money. Read each command before you run it.
What this app does differently
The demo deployment runs with DEMO_MODE=true, which activates:
- Public demo banner — a sitewide notice that data resets daily.
- Rate limiting — stricter per-IP limits to prevent abuse.
- Daily reset — a scheduled job truncates instances and step data at UTC midnight.
- Public API token — a well-known
OPENSOP_API_TOKENvalue so visitors
can try the REST API without signing up. - Read-only process definitions — the UI disallows editing or deleting
built-in demo processes (creation still works in sandboxed form). /docsroute — interactive API documentation enabled in the demo.
These features are gated on ENV["DEMO_MODE"] in the application code and
have no effect on the primary deployment.
0. Prerequisites
-
fly auth login(one-time). -
config/master.keyexists locally:ls config/master.keyIf it is missing, recover it from your password manager — you cannot
decrypt production credentials without it. -
A Fly organisation that can create apps and Postgres clusters.
1. Create the app
flyctl apps create opensop-demo
Do not run fly launch — we already have fly.demo.toml.
2. Create the Postgres cluster
flyctl postgres create \
--name opensop-demo-db \
--region ord \
--initial-cluster-size 1 \
--vm-size shared-cpu-1x \
--volume-size 1
- 1 GB volume is enough for demo data that resets daily.
- Match
--regiontoprimary_regioninfly.demo.toml(ord). - Save the connection string Fly prints — you need it in step 4.
3. Attach Postgres to the app
flyctl postgres attach opensop-demo-db --app opensop-demo
This creates a opensop_demo role + database and sets DATABASE_URL as a
secret on the app automatically.
4. Create cache + queue databases
Open a psql session on the cluster:
flyctl postgres connect -a opensop-demo-db
Inside psql:
CREATE DATABASE opensop_demo_cache OWNER opensop_demo;
CREATE DATABASE opensop_demo_queue OWNER opensop_demo;
\q
Now set the two extra URL secrets. They reuse the same host and credentials
as DATABASE_URL; only the database name at the end changes. Fly attach URLs
look like:
postgres://opensop_demo:PASSWORD@opensop-demo-db.flycast:5432/opensop_demo
Replace the trailing /opensop_demo with the new database names:
flyctl secrets set \
CACHE_DATABASE_URL="postgres://opensop_demo:PASSWORD@opensop-demo-db.flycast:5432/opensop_demo_cache" \
QUEUE_DATABASE_URL="postgres://opensop_demo:PASSWORD@opensop-demo-db.flycast:5432/opensop_demo_queue" \
--app opensop-demo
5. Set secrets
flyctl secrets set \
RAILS_MASTER_KEY="$(cat config/master.key)" \
OPENSOP_API_TOKEN="demo-public-token-resets-daily" \
OPENSOP_BASE_URL="https://demo.opensop.ai" \
--app opensop-demo
OPENSOP_API_TOKENis the public demo token. The same value powers
Sop::ApplicationControllerauth (visitors send it as theX-SOP-Token
header) AND the value displayed on the demo homepage —Opensop::DemoMode.api_token
readsOPENSOP_API_TOKENfirst. Setting one secret is enough.OPENSOP_BASE_URLis used by the runtime to build webhook callback URLs.DEMO_API_TOKENis not required. It exists only as a fallback for local
development and is only consulted whenOPENSOP_API_TOKENis unset. Don't
bother setting it on Fly.
6. Deploy
flyctl deploy --config fly.demo.toml
What happens:
- Fly builds the image from the repo's
Dockerfile(same image as primary). - A release machine runs
./bin/rails db:prepare— migrates all three
databases (primary, cache, queue). - The web machine boots. Thruster listens on port 3000; Fly's proxy handles
TLS and routes traffic to it. - Solid Queue starts inside Puma (
SOLID_QUEUE_IN_PUMA=true). - With
min_machines_running = 0the machine stops when idle and wakes on
the next request (~5 s cold start).
7. Provision the TLS certificate
flyctl certs create demo.opensop.ai --app opensop-demo
8. Point DNS at the app
In your DNS registrar / DNS provider, add one of the following:
Option A — CNAME (simplest, works for most providers):
demo CNAME opensop-demo.fly.dev.
Option B — A + AAAA records (required if the registrar forbids CNAME on the
apex / you need exact IPs):
flyctl certs show demo.opensop.ai --app opensop-demo
Use the A and AAAA record values that command prints.
9. Verify
Certificate validation can take up to 5 minutes after DNS propagates.
# Poll until the cert is active:
flyctl certs show demo.opensop.ai --app opensop-demo
# Then confirm the app responds:
curl -i https://demo.opensop.ai/up
# Expected: HTTP/2 200
Daily costs (rough estimate)
| Resource | Size | Approx. cost |
|---|---|---|
| App machine (shared-cpu-1x, 256 MB) | Billed per second when running | ~$0–3/mo at low traffic with min_machines_running = 0 |
| Postgres cluster (shared-cpu-1x) | Always-on | See Fly pricing |
| Postgres volume (1 GB) | Always-on | See Fly pricing |
Rule of thumb: the smallest viable footprint (sleeping app + smallest
Postgres + 1 GB volume) is roughly $5–10/month based on Fly's published
rates. Verify current prices at https://fly.io/docs/about/pricing/ before
committing — Fly adjusts pricing periodically.
To reduce Postgres cost further, consider the
Fly Postgres HA single-node options or
sharing the primary deployment's cluster (create the demo databases there
instead of a dedicated cluster).
Updating the demo
After the initial deploy, re-deploy with:
flyctl deploy --config fly.demo.toml
No other flags needed — secrets and attached databases persist between deploys.
Troubleshooting
release_command failed
flyctl releases --app opensop-demo
flyctl logs --app opensop-demo
Most common cause: a missing URL secret. Verify all three are set:
flyctl secrets list --app opensop-demo
# Should show: DATABASE_URL, CACHE_DATABASE_URL, QUEUE_DATABASE_URL,
# RAILS_MASTER_KEY, OPENSOP_API_TOKEN, OPENSOP_BASE_URL
App boots but /up returns 500
flyctl logs --app opensop-demo
Look for ActiveRecord::ConnectionNotEstablished or PG::ConnectionBad —
one of the three database URL secrets is missing or points at a database that
does not exist yet. Re-run steps 4–5.
Need a shell on the machine
flyctl ssh console --app opensop-demo
From there: ./bin/rails console, inspect /rails/log/, or
psql $DATABASE_URL.