Skip to content

Standalone F8 Studio

F8 Studio can ship baked into the all-in-one container, served same-origin from the API app’s wwwroot (Running). You can also deploy it standalone: a small static (nginx) container that serves the SPA and is pointed at an arbitrary Fallen-8 REST data plane at container start. This decouples where the UI runs from where the data plane runs, without a rebuild.

The default npm run env:up runs F8 Studio this way: its own container talking to the data plane. The all-in-one (UI baked into the API container) remains available with a bare docker compose up.

The UI runs in the browser, so it needs the browser-reachable URL of the REST API. A standalone build carries a tiny config.js that sets a global before the app loads:

window.__F8_CONFIG__ = { apiUrl: "https://graph.example.com" };

The container’s entrypoint rewrites that one file from the F8_API_URL environment variable every time it starts, so one built image can front any endpoint. An empty apiUrl (the default) means same origin, which is the all-in-one behaviour. The Connect screen still lets a user add or switch to other instances by hand.

A quote, backslash, or newline in F8_API_URL would break out of that JS string literal, so the entrypoint refuses to start instead of writing a broken config.js: nginx never comes up and the log carries f8-config: F8_API_URL contains a quote or backslash; refusing to start (or … contains a newline …). Check docker logs when the UI container will not start.

The default dev environment already runs this topology: a UI-less data plane plus the standalone UI container (the full set of topologies is in Running):

npm run env:up # REST data plane on :8080, F8 Studio UI on :8081

F8_UI_PORT (default 8081) sets the UI’s host port and F8_PORT (default 8080) the data plane’s. A bare docker compose up (no overlay) instead runs the all-in-one, with the UI baked into the API container on :8080.

A UI on one origin calling a data plane on another is a cross-origin request (a different port counts), and Fallen-8’s CORS is deny-all by default. Allow the UI’s origin on the data plane:

Fallen8__Security__AllowedCorsOrigins__0=http://localhost:8081

The indexed __0 suffix is required to bind the array; a bare key does not. The split overlay wires this for you. If a cross-origin instance reads as “unreachable” on the Connect screen while the data plane is actually up, a missing allow-list entry is the usual cause, and Studio surfaces that hint for exactly this case. See Security.

A model backend the browser calls directly needs the UI origin allowed too, on that backend: OLLAMA_ORIGINS for your own Ollama. The split overlay sets both allow-lists. See NL assist.

The image builds from the repo root (so the SPA build reaches the bundled sample datasets):

docker build -f fallen-8-web-ui/Dockerfile -t f8-studio .
docker run -p 8081:80 -e F8_API_URL=https://graph.example.com f8-studio

It serves the SPA with a client-side-routing fallback, ships the bundled sample datasets at /samples (where the gallery fetches them same-origin), and never caches config.js (so an endpoint change takes effect on the next load). Hosting under a sub-path is not supported: assets resolve from the origin root.

The endpoint from config.js is a managed default instance: it is synthesized fresh on every load (so changing F8_API_URL propagates) and cannot be removed. Because it is never persisted, it also carries no credential: config.js sets only apiUrl, and an API key typed into the managed record with Edit works for that session and is gone on the next load (the instance reads unauthorized again).

So against a key-secured data plane each user registers a personal instance pointed at the data plane’s URL with the key: personal instances, keys included, persist in the browser untouched. See Security for the key itself, and F8 Studio for the instance model and the Connect screen.