Self-contained, portable deployment of the AI Studio stack (WB-229). Runs on any Docker host — an Azure VM, AWS, on-prem — with no cloud-specific glue.
Deploying onto the company Swarm cluster instead? See
tools/deployment/— same images, Traefik/ACR/Ansible orchestration aligned with the workflow-builder repo.
| Service | Image | Role | Exposed |
|---|---|---|---|
web |
ai-studio-web (nginx) |
Serves the SPA, proxies /api to the backend |
${WEB_PORT} (only one) |
backend |
ai-studio-runtime |
Hono REST + SSE event stream | internal |
worker |
ai-studio-runtime |
Temporal worker, makes the OpenRouter LLM calls | internal |
temporal |
temporalio/auto-setup pinned |
Workflow engine | internal |
app-db |
postgres:16 |
Workflow snapshots + execution events | internal |
temporal-db |
postgres:16 |
Temporal's own state store | internal |
temporal-ui |
temporalio/ui pinned |
Debug only (--profile debug) |
127.0.0.1:8233 |
Both images build from one Dockerfile (deploy/ai-studio/Dockerfile) with the
repo root as context. Backend and worker share a single image and differ only
in the compose command. Database migrations are applied by the backend at
boot (drizzle-orm's programmatic migrator) — there is no separate migration
service or step.
cd deploy/ai-studio
cp .env.example .env # set OPENROUTER_API_KEY
docker compose up -d --buildFirst boot: the backend applies migrations and only then starts serving (its
healthcheck gates the worker). The worker crash-loops for ~30s until Temporal
finishes auto-setup — that's expected, restart: unless-stopped converges it.
Verify:
curl -s http://localhost:8080/api/health # {"status":"ok"}
# open http://localhost:8080, run the "Sales Inquiry Pipeline" templateTwo independent controls; both must be in place before the URL goes public:
- OpenRouter Guardrail (hard $/day ceiling, no code involved): openrouter.ai → Settings → Guardrails → daily spend limit, e.g. $5/day (resets 00:00 UTC). When hit, OpenRouter rejects calls and the demo pauses — it cannot overspend. Keep the account balance low (~$20) as the absolute ceiling.
- Per-IP rate limit (already on in this compose): defaults to 10
executions/min and 50/day per IP, tunable via
RATE_LIMIT_EXECUTE_PER_MINUTE/RATE_LIMIT_EXECUTE_PER_DAY. In-memory, single-replica by design; counters reset on backend restart.
At the defaults, a worst case full Guardrail day costs $5; a typical 3-LLM-call template run on Mistral Small 3.2 costs ~$0.0004.
The web container speaks plain HTTP on the internal port. Pick one:
- Existing ingress (Azure Application Gateway / Front Door, an nginx that
already routes your other web apps, …): point it at
WEB_PORT, setWEB_BIND=127.0.0.1if the ingress runs on the same host. SSE caveat: the ingress must not buffer/api/executions/*/streamresponses and needs a read timeout above 60s (the stream heartbeats every 15s). - Standalone VM: run a host-level Caddy
(
reverse_proxy localhost:8080— automatic Let's Encrypt, SSE-safe out of the box) or certbot'd nginx in front, and firewall everything except 80/443.
Keep 8233 (Temporal UI) and the Postgres ports unreachable from outside — this compose never publishes them; don't undo that.
See .env.example — every variable is documented there.
Swapping the LLM is a one-liner: change AI_MODEL to any
OpenRouter model id and
docker compose up -d worker.
docker compose logs -f backend worker # tail the apps
docker compose --profile debug up -d # Temporal UI on 127.0.0.1:8233
docker compose up -d --build # deploy a new version (backend re-applies migrations at boot)
docker compose down # stop (volumes survive)
docker exec ai-studio-app-db-1 pg_dump -U wb workflow_builder > backup.sqlWorkflow data is treated as ephemeral for the public demo — losing the volumes is acceptable; there is nothing precious in them.
- No login. The API is open (
WB_AUTH_PORT=allow-all); anyone with the URL can create and run workflows within the rate limits. The SDK has anAuthPortseam for wiring real auth later. - Single backend replica. The rate limiter is process-local. Scaling out needs a shared store (Redis) — deferred to the scale-ready task.
temporalio/auto-setupis dev-grade. Fine for a demo; move to Temporal Cloud or an operated cluster for sustained load.- Anyone-can-edit demo content. Visitors share one workspace; data is wiped whenever you decide to recreate the volumes.