Run it
The whole stack from one docker-compose file, behind one domain, with a smoke test that proves the pipeline end-to-end.
Start the stack
git clone <your-clone-url> quickable && cd quickable
docker compose -f deploy/compose.yaml up -d --build --waitThat is the entire deployment. What starts:
| Service | Role |
|---|---|
caddy | The only published port (:8080 by default). Every other service lives on internal networks. |
nats | The broker: JetStream, websocket, the decentralized-auth resolver. |
bootstrap | One-shot: mints operator/system/platform credentials and the nats config onto the creds volume, then exits. Idempotent across restarts. |
core | The application flows. Stateless; scales with --scale core=N. |
transform | The build worker — read-only rootfs, tmpfs scratch, nats-only network. |
gateway | REST /v1, MCP /mcp, shells /a/…, bundles /bundles/…. |
victoria-logs / victoria-metrics | Log and metric backends (operator-only, behind basic auth). |
The single domain
Caddy routes everything on one hostname (deploy/Caddyfile):
| Path | Where it goes |
|---|---|
/v1/* | REST projection of the API registry (gateway) |
/mcp | MCP streamable-HTTP endpoint (gateway) |
/a/<ns>/<app> | The rendered app shell (gateway) |
/bundles/* | Built app bundles (gateway → object store, read-only) |
/nats | Websocket proxy to the broker — the native NATS path for browsers and scripts |
/docs/* | This documentation site (static files, zero runtime) |
/o11y/logs*, /o11y/metrics* | VictoriaLogs / VictoriaMetrics, basic-auth admin |
/healthz | Gateway health |
| everything else | gateway (404s outside its sealed route inventory) |
For a real hostname with automatic TLS set QUICKABLE_DOMAIN=docs.example.com (and expose
443); the default :8080 is plain HTTP for local runs. SIGNUP_MODE=invite gates signup
behind invite tokens.
Serving this docs site
The /docs route serves static files from /srv/docs inside the caddy container. The
export is produced by pnpm --filter @quickable/docs build into apps/docs/out/; provide
it to caddy either as a read-only volume on the caddy service
(../apps/docs/out:/srv/docs:ro — one line in deploy/compose.yaml) or by baking the
directory into a caddy image at /srv/docs. Until the files are present the route simply
404s; nothing else depends on it (the site is an enumerated static surface, A2′).
Prove it works
The smoke test exercises the public edge only — signup, native ws connect, bearer mint, app create, material put, transform build, rendered shell, served bundle:
pnpm install && pnpm -r build
node packages/qctl/dist/index.js smoke run http://localhost:8080 /tmp/smoke.jsonEvery step prints PASS/FAIL. Afterwards, restart core (docker compose -f deploy/compose.yaml restart core) and re-verify that the same bearer and the same app
survive a restart (A8 restart-idempotency, observed at the edge):
node packages/qctl/dist/index.js smoke verify http://localhost:8080 /tmp/smoke.jsonTear down
docker compose -f deploy/compose.yaml down -v-v removes the named volumes (creds, nats-data, the Victoria stores) — the complete
durable state (A8′). Ramp-to-zero leaves nothing behind.