Skip to content

Choosing an Image

Sipke Schoorstra edited this page Aug 12, 2026 · 3 revisions

Choosing an Image

Two decisions: which image, and — if it includes Studio — which Studio hosting model.

The three images

Image Elsa API Studio UI Choose when
runtime-*-server ✅ — Studio is deployed separately, or you don't need a UI at all (headless workflow execution driven by API and triggers)
runtime-*-studio — ✅ You already run an Elsa API and want a designer pointed at it
runtime-*-combined ✅ ✅ One container, one origin. Simplest to run and to secure

Community images carry a ce- prefix and are public on GHCR — ghcr.io/valence-works/runtime-ce-combined. Paid images drop the prefix and live in the private registry — valenceruntimeimages.azurecr.io/runtime-combined. The bits are identical; see Pulling Images.

Which should I use?

Start with combined. One container, one port, one origin, no CORS. Most deployments never need more than this.

Split into server + studio when you want to scale the API independently of the UI, expose the API publicly while keeping Studio on an internal network, deploy them on different release cadences, or run the UI in a DMZ. The cost is a CORS configuration and a second thing to operate.

Use server alone when workflows are triggered by API calls, HTTP endpoints or schedules and nobody needs a designer in that environment — a common production shape where authoring happens in a staging environment and workflow definitions are promoted.


Studio hosting model: WebAssembly or Blazor Server

Both the studio and combined images can run Studio either way. Set Studio__HostingModel. The default is WebAssembly.

This choice determines who calls the Elsa API — and getting it wrong is the most common setup failure.

WebAssembly (default)

Studio runs in the browser. The browser calls the Elsa API directly.

Browser ──────────────► Elsa API
   │  (downloads Studio)
   └────► Studio host
  • Set Studio__Client__Backend__Url — note the Client segment. This URL is served to the browser, so it must be reachable from the user's machine. http://elsa-server:8080 is a Docker network name and will not resolve in a browser.
  • The server needs a CORS origin allowing the Studio origin: Elsa__Cors__AllowedOrigins__0.
  • Only the Studio:Client configuration section is sent to the browser. Don't put secrets in it.

Pick this when you want browser-side interactivity, lower server load, and the API is reachable from wherever users are.

Blazor Server

Studio runs on the server. The Studio container calls the Elsa API; the browser holds a SignalR connection to Studio.

Browser ──(SignalR)──► Studio host ──────► Elsa API
  • Set Backend__Url — no Client segment. This is resolved inside the container, so a Docker service name like http://elsa-server:8080/elsa/api is correct here.
  • No CORS configuration needed — the browser never calls the API directly.
  • The API never has to be publicly reachable.

Pick this when the Elsa API is on a private network, or you want the API not exposed to browsers at all.

Side by side

WebAssembly Blazor Server
Config key Studio__Client__Backend__Url Backend__Url
URL must resolve from The user's browser Inside the container
Docker service names work? ❌ No ✅ Yes
CORS needed on server? ✅ Yes ❌ No
API must be browser-reachable? ✅ Yes ❌ No
Latency sensitivity Tolerant Needs a stable connection
Server cost per user Low Higher (a live circuit per user)

🔑 The rule: Studio__Client__* is browser-facing. Everything else is container-facing. If Studio loads but shows connection errors, you almost certainly set a container-internal URL in the WebAssembly client setting. → Troubleshooting


Examples

Combined, WebAssembly (default) — nothing extra needed:

docker run -d -p 8080:8080 \
  -e CShells__Shells__Default__Features__Identity__SigningKey="$(openssl rand -base64 32)" \
  ghcr.io/valence-works/runtime-ce-combined:latest

Combined, Blazor Server:

docker run -d -p 8080:8080 \
  -e Studio__HostingModel=BlazorServer \
  -e Backend__Url=http://localhost:8080/elsa/api \
  -e CShells__Shells__Default__Features__Identity__SigningKey="$(openssl rand -base64 32)" \
  ghcr.io/valence-works/runtime-ce-combined:latest

Separate, WebAssembly — note the browser-reachable URL and the matching CORS origin:

docker network create elsa

docker run -d --name elsa-server --network elsa -p 8080:8080 \
  -e Elsa__Cors__AllowedOrigins__0=http://localhost:8081 \
  -e CShells__Shells__Default__Features__Identity__SigningKey="$(openssl rand -base64 32)" \
  ghcr.io/valence-works/runtime-ce-server:latest

docker run -d --name elsa-studio --network elsa -p 8081:8080 \
  -e Studio__HostingModel=WebAssembly \
  -e Studio__Client__Backend__Url=http://localhost:8080/elsa/api \
  ghcr.io/valence-works/runtime-ce-studio:latest

Separate, Blazor Server — container-internal URL, no CORS:

docker run -d --name elsa-studio --network elsa -p 8081:8080 \
  -e Studio__HostingModel=BlazorServer \
  -e Backend__Url=http://elsa-server:8080/elsa/api \
  ghcr.io/valence-works/runtime-ce-studio:latest

Every container listens on 8080 internally. The 8081:8080 mapping is what puts Studio on a different host port.


Next: Pulling Images · Running the Images · Configuration

Clone this wiki locally