โ† All docs  ยท  Home

Hosting the Freedback servers (default + demo + discovery)

The servers are long-running HTTP/1.1 binaries with a Dockerfile, so any container host works. This guide uses Fly.io (always-on-ish, free allowance, deploys straight from the Dockerfile, free volumes for durable storage) and notes a Hugging Face Docker Space alternative. GitHub Pages can't host them โ€” it's static-only (it serves the @context/ontology/shapes + the widget demo).

Fly.io (recommended)

Configs live in deploy/fly/, one app each:

Config Fly app Role Storage
default.toml freedback-default the default server (dogfooding publishes here) persistent โ€” RocksDB on a Fly volume
demo.toml freedback-demo demo-pages playground ephemeral (in-memory; may reset)
discovery.toml freedback-discovery registry ephemeral (re-announces are cheap)

They run the prebuilt image's binaries via [processes], scale to zero when idle, and force HTTPS. demo = live playground, default = persistent.

One-time

# 1. Install + log in
curl -L https://fly.io/install.sh | sh
fly auth login

# 2. Create the apps (run from the repo root so the build context is the
#    workspace and ./Dockerfile is found). --copy-config keeps our toml.
fly launch --no-deploy --copy-config --config deploy/fly/default.toml    --name freedback-default
fly launch --no-deploy --copy-config --config deploy/fly/demo.toml       --name freedback-demo
fly launch --no-deploy --copy-config --config deploy/fly/discovery.toml  --name freedback-discovery

# 3. The default server's persistent volume (one-off; recent flyctl also
#    auto-creates it on first deploy thanks to [mounts] initial_size).
fly volumes create freedback_data --size 1 --region cdg --app freedback-default

Pick app names you own; if you change them, update the app = line in each toml and the FREEDBACK_BASE_URL once you know the public host.

Deploy

fly deploy --config deploy/fly/default.toml
fly deploy --config deploy/fly/demo.toml
fly deploy --config deploy/fly/discovery.toml

Each app gets a https://<app>.fly.dev URL immediately. Check them (/ also answers with a small index โ€” handy for a browser click):

curl https://freedback-default.fly.dev/.well-known/freedback
curl https://freedback-demo.fly.dev/.well-known/freedback
curl https://freedback-discovery.fly.dev/.well-known/freedback

Automated deployment (GitHub Actions CD)

.github/workflows/fly-deploy.yml redeploys all three apps to Fly on every push to main that touches the server code / build / Fly config (crates/**, Cargo.*, Dockerfile, deploy/fly/**), and on manual Run workflow. It builds on Fly's remote builders (no Docker in CI) and creates the apps on first run (idempotent), so you don't need flyctl locally at all โ€” the CLI steps above are only for a one-off manual deploy or local testing.

The job is guarded: it runs only when a FLY_API_TOKEN repo secret is present, and is skipped cleanly otherwise (it never fails a push). Turning it on is all web UI โ€” nothing to install:

  1. Fly.io โ€” billing. Add a payment method (Fly Dashboard โ†’ your account โ†’ Billing). The configs scale to zero, so a demo costs ~nothing, but Fly requires a card on file.
  2. Fly.io โ€” token. Create an account-level access token at https://fly.io/user/personal_access_tokens ("Create access token") and copy it. Use this one, not a per-app deploy token โ€” a deploy token is scoped to an app that must already exist, which is exactly backwards here (the workflow creates the apps itself on first run). Don't use Apps โ†’ Launch an App either; that wizard deploys immediately from its own guess at a config and doesn't know about deploy/fly/*.toml.
  3. GitHub โ€” secret. In the repo, Settings โ†’ Secrets and variables โ†’ Actions โ†’ New repository secret, name it FLY_API_TOKEN, paste the token.
    • (Optional) if you deploy under a non-personal Fly org, add a repo variable FLY_ORG with the org slug (defaults to personal).

Then push to main (or Actions โ†’ Deploy (Fly.io) โ†’ Run workflow) and it provisions + deploys the apps. The app names in the tomls (freedback-default, freedback-demo, freedback-discovery) are global on Fly โ€” if one is taken, change the app = line in the toml (and FREEDBACK_BASE_URL) before the first run.

Where the deployed URL shows up

Each successful run records a GitHub Deployment (via the job's native environment: block โ€” no extra action or script needed). That surfaces the live URL in the usual GitHub places:

The URL used is always the guaranteed https://<app>.fly.dev host (not a branded subdomain), since that resolves immediately after every deploy even before fly certs add / DNS is set up for a custom domain.

Production smoke test

.github/workflows/prod-smoke.yml runs an end-to-end check against the REAL deployments โ€” after every successful Fly deploy, weekly, and on demand. It verifies the live site (landing page, pinned @context, widget script), then runs a full lifecycle against the live backend + discovery: publish a signed rating with a run-local P-256 key โ†’ read it back โ†’ resolve it through the registry โ†’ assert a different key cannot erase it (403) โ†’ erase it with the author's key (DELETE, ADR 0021) โ†’ assert it is gone (empty read, 410 Gone, tombstone listed). The default (persistent) server gets its own publish โ†’ read โ†’ erase โ†’ 410 round trip too, and both servers are announced to the registry. The run erases its own data (traps guarantee cleanup even on mid-test failure), so production is left clean. No secrets โ€” deletion is authorized by authorship.

Branded subdomains

Point subdomains of freedback.net at the apps and set certs:

fly certs add default.freedback.net   --app freedback-default
fly certs add demo.freedback.net      --app freedback-demo
fly certs add discovery.freedback.net --app freedback-discovery

fly certs add prints the exact DNS records to create at the registrar โ€” for a subdomain the simple, correct record is a CNAME:

default    CNAME  freedback-default.fly.dev.
demo       CNAME  freedback-demo.fly.dev.
discovery  CNAME  freedback-discovery.fly.dev.

(Fly may additionally ask for an _acme-challenge CNAME to issue the certificate โ€” create whatever fly certs add prints, then fly certs check <hostname> --app <app> until it reports Ready.) The tomls already set each FREEDBACK_BASE_URL to its branded host, so no redeploy is needed once DNS resolves.

Durable feedback storage

freedback-default (default.toml) IS the durable app: the RocksDB image (FEEDBACK_FEATURES = "rocksdb" build arg), FREEDBACK_ROCKSDB_PATH = "/data/db", and a [mounts] volume are wired in already. Two operational notes:

(The demo + discovery apps stay in-memory on purpose โ€” the demo is a playground and re-announces are cheap.)

Hugging Face Docker Space (zero-cost alternative)

Create a Docker Space and push this repo's image. Add a Space README.md header:

---
title: Freedback demo
sdk: docker
app_port: 8080
---

with a Dockerfile whose CMD is freedback-feedback-server. Free CPU; the Space sleeps when idle and storage is ephemeral, so use the in-memory build. Good for a click-to-deploy demo; less suited to durability than Fly.

Wiring the demo together

Once both are up:

  1. The discovery server verifies + lists announced feedback servers (POST /announce, GET /servers, GET /resolve?target=).
  2. Point the Pages widget showcase at the live server instead of its in-browser mock by setting the widgets' data-read / data-publish to the feedback/collection URLs (e.g. https://demo.freedback.net/annotations/). (A small follow-up in demo-react/ swaps the mock for these URLs.)

Why not serverless (Workers / Deno Deploy)?

Those run WASM/edge functions; Freedback's wasm is browser-only (wasm32-unknown-unknown, not WASI), and the servers are native axum binaries โ€” so a container host (Fly/HF/Render/Koyeb/Oracle Always-Free) is the fit, not an edge-function platform.