Demo applications: the full round trip
This explains the complete life of a WBSP demo (aws.demo) — the "Try Me"
experience: a real app running on real, wire-compatible datastores, launched on
demand at a unique URL, and torn down automatically. It covers every stage
(publish → launch → status → reap), who does what (app creator/operator, the
wbsp.ai website, an end user, the platform), and which tool drives each step
(wbsp CLI and/or the website).
The mental model
A demo is not a persistent deployment. It's an ephemeral, per-visitor copy of an app that:
- runs the app's real image plus bundled PostgreSQL + Redis inside one pod (no shared RDS/ElastiCache), seeded from the app's standard SQL dump;
- lives at its own unguessable URL (
<app>-<token>.wbsp-demo.com); - is isolated (its own namespace, gVisor sandbox where available);
- auto-expires (default 10 minutes) and is reaped to nothing — zero compute when idle (scale-to-zero);
- signs the visitor in via shared SSO — if they're logged into wbsp.ai, they're logged into the demo, no second login.
It runs in three parts, done by three different actors, and there is exactly
one identifier that ties them together: the catalog variant id (from the
app's wbsp.yaml variant:). The CLI reads it from the file, the website holds it
as a route param, and wbsp-api uses it as the lookup key — nobody translates
between app names, repo names, and ids.
PUBLISH (once per release) LAUNCH (on demand) REAP (automatic)
app creator / operator website button / CLI the platform
─────────────────────── ────────────────── ────────────────
build image + bake seed → start an ephemeral pod → TTL → grace page → gone
push to ECR inject datastores + SSO namespace deleted
write per-variant config return a unique URL scale-to-zero
(nothing running yet) (warming → running)Stage 1 — Publish (build the demo image)
Who: the app creator or a platform operator. Tool: wbsp CLI.
When: once per app release. Result: nothing runs; an image + config sit
ready in the platform.
Prerequisites in the app's wbsp.yaml:
- a
variant:id (mandatory — this is the demo's key everywhere), - a
sample_data:block pointing at standard.sqlseed files, - a
demodestination — justdemo: {}. The name is reserved: the platform supplies its whole identity (type,mode,tenant,cluster,enclave), and declaring any of those keys is an error.
wbsp deploy --destination demoWhat the platform does:
- Builds a self-contained image — the app layered on the bundled
Postgres+Redis+supervisor stack, with the sample data baked into the seed path
— for
linux/amd64, and pushes it to ECR (wbsp-v-<short>-app-demo:demo). - Writes a per-variant config Secret (
wbsp-demo-<tenant>-<variant>) holding the image ref, the app'senv:+ resolvedsecrets:, and markers (WBSP_DEMO_APP_NAMEfor the display URL,WBSP_DEMO_REDISto gate Redis). The launch API — which never sees the app'swbsp.yaml/.env— reads this at launch time.
No instance is started. The app is now launchable.
Stage 2 — Launch (start a personal instance)
Who: an end user clicking "Try Me" on wbsp.ai (primary), or an operator via
the CLI (parity). Tool: the website → wbsp-api launch API; or wbsp demo launch. Result: a warming, then running, per-user instance at a unique URL.
Via the website (the real path)
- A signed-in wbsp.ai visitor clicks Try Me on a variant.
- The website's server calls the platform, passing the variant id it already
holds and its service token:
POST https://api.wbsp.ai/api/v1/apps/{tenant}/{variant}/demo/launch. - The platform (wbsp-api), server-side:
- reads the per-variant config Secret (image + app env),
- injects the discrete datastore contract (
DATABASE_*/REDIS_*pointing at the in-pod engines),WBSP_SAMPLE_MODE=true, the container port, and the per-instance URL, - overlays the shared demo HAP (SSO) config so the visitor's wbsp.ai session carries in,
- creates the ephemeral k8s objects: a per-instance Namespace, a single
Pod (gVisor, TTL backstop), a Service, and a Traefik IngressRoute
on
<app>-<token>.wbsp-demo.com(wildcard TLS), - returns
202 { id, url, status:"warming", expires_at, seconds_remaining }.
- The website redirects the visitor to
url. First response takes ~30–60s while the pod boots Postgres+Redis, loads the seed, and starts the app. Then the app serves, already signed in, with seeded data.
The website never handles image refs, HAP client ids, or secrets — the platform
injects all of it. Concurrency is capped platform-wide (a 409 means "at
capacity").
Via the CLI (operator/CI parity)
Run in the app directory — the variant comes from wbsp.yaml, the tenant from the
demo destination:
wbsp demo launch
# url: https://ai-native-crm-ab12cd34.wbsp-demo.com
# status: warming
# expires: 2026-07-15T10:40:00ZConfig-less callers pass --tenant <t> --variant <id>. (--backdoor is a
separate operator-only path that builds against EKS directly with an explicit
--image; the website always uses the API.)
Stage 3 — Status (is it available? how long left?)
Who: the website (to decide whether to show the button, and to drive a
countdown), or an operator. Tool: the launch API / wbsp demo status.
- App-level availability — call this before offering a demo:
GET /apps/{tenant}/{variant}/demo→{ published, prerequisites_ok, launchable, capacity:{active,max,at_capacity}, instances:[…] }. Gate the button onlaunchable. - Per-instance — for the countdown after launch:
GET /apps/{tenant}/{variant}/demo/instances/{id}→{ status, expires_at, seconds_remaining }.
wbsp demo status # in the app dir
# variant: 7a1d8030-… (ai-native-crm)
# launchable: true
# capacity: 1/10 active
# instances: 1 running
# - ai-native-crm-ab12cd34 [running] https://… reaps in 8m41sseconds_remaining is computed server-side; the website renders "N min left" from
it (or from expires_at).
Stage 4 — Reap (automatic shutdown)
Who: the platform, unattended. Tool: the wbsp-api reaper (a background loop) plus a pod-level backstop. No one calls anything — this just happens.
Each instance has an expires_at (launch time + timeout_minutes, default 10).
The reaper ticks periodically and moves every instance through a two-phase
teardown:
- TTL reached → the app Pod (and its in-pod Postgres/Redis) is deleted, and for a short grace window the same URL serves a "this demo has ended" page instead of a connection error.
- Grace elapsed → the whole Namespace, the route, and the registry entry are deleted. The URL stops resolving. All demo data is gone.
Defense in depth: the Pod also carries activeDeadlineSeconds, so it
self-terminates at the TTL even if the reaper is down. A visitor can also end it
early:
wbsp demo remove --name <instance-id> # or the website's DELETEWhen no instances are live, nothing runs — only the prepared image sits in ECR (at-rest cost only).
Who does what — summary
| Stage | Actor | CLI | Website | Platform (wbsp-api) |
|---|---|---|---|---|
| Publish | app creator / operator | wbsp deploy --destination demo (build+push+config) | — | stores image + per-variant config |
| Launch | end user (or operator) | wbsp demo launch (parity) | "Try Me" → POST …/demo/launch | starts pod, injects datastores + SSO, returns URL |
| Status | website / operator | wbsp demo status | GET …/demo (+ instance poll) | reports availability + countdown |
| Reap | platform | wbsp demo remove (early) | DELETE …/instances/{id} (early) | TTL → grace page → deleted; scale-to-zero |
Auth in one line
Demo apps use a shared demo OIDC client in the wbsp tenant (same tenant as
wbsp.ai), injected by the platform at launch. A visitor already signed into wbsp.ai
is signed into the demo via silent SSO; a signed-out visitor sees the tenant
login page (still works, just not silent).
Sequence (end to end)
sequenceDiagram
participant Creator as App creator/operator
participant CLI as wbsp
participant Site as wbsp.ai website
participant API as wbsp-api
participant Pod as Demo pod
participant Reaper as Platform reaper
Creator->>CLI: deploy --destination demo
CLI->>API: build+push image, write per-variant config
Note over API: nothing running yet
Site->>API: POST /apps/{tenant}/{variant}/demo/launch
API->>Pod: create ns + pod (PG+Redis+app) + route
API-->>Site: 202 {url, status:warming, expires_at}
Site-->>Pod: redirect visitor to url (silent SSO)
Note over Pod: warming ~30-60s → running (seeded)
Site->>API: GET /apps/{tenant}/{variant}/demo/instances/{id}
API-->>Site: {status:running, seconds_remaining}
Note over Reaper: at expires_at
Reaper->>Pod: delete app pod → serve "demo ended" (grace)
Note over Reaper: after grace
Reaper->>Pod: delete namespace + route (gone, scale-to-zero)See also
user-docs/HOWTO-bootstrap-developer.md— set upwbspto run these commands.notes/handoff-wbsp-website-demo.md— the website integration contract (endpoints, auth, gating).user-docs/application-creator-guide.md— thesample_data:+ demo-destination config.